Entity relationship diagrams

The “crow’s foot” that connects the tables together shows us how the columns in one table relate to the columns in another table. ## Types Of Statements A few statements include: * CREATE TABLE is a statement that creates a new table in a database. * DROP TABLE is a statement that removes a table in a database. * SELECT allows you to read data and display it. This is called a query.

SELECT & FROM Statements

Here you were introduced to two statements that will be used in every query you write * SELECT - what columns you want back. * FROM - what table you are querying from.

SELECT id, account_id, occurred_at
FROM orders;

LIMIT Statement

The LIMIT statement is useful when you want to see just the first few rows of a table. This can be much faster for loading than if we load the entire dataset. The LIMIT command is always the very last part of a query. An example of showing just the first 10 rows of the orders table with all of the columns might look like the following:

SELECT *
FROM orders
LIMIT 10

ORDER BY Statement

SELECT id, occurred_at, total_amt_usd
FROM orders
ORDER BY occurred_at
LIMIT 10;

We can ORDER BY more than one column at a time. The statement sorts according to columns listed from left first and those listed on the right after that.

SELECT *
FROM orders
ORDER BY occurred_at, total_amt_usd
LIMIT 10;

WHERE Statements

Using the WHERE statement, we can subset out tables based on conditions that must be met.

SELECT *
FROM orders
WHERE gloss_amt_usd >= 1000
LIMIT 5;

The WHERE statement can also be used with non-numerical data. We can use the = and != operators here. You also need to be sure to use single quotes

SELECT name, website, primary_poc
FROM accounts
WHERE name = 'Exxon Mobil';

Arithmetic Operators

Derived Columns

Creating a new column that is a combination of existing columns is known as a derived column. ### Order of Operations Remember BOMDAS from maths class? The same order of operations apply when using arithmetic operators in SQL.

SELECT id, account_id, 
       poster_amt_usd/(standard_amt_usd + gloss_amt_usd + poster_amt_usd) AS post_per
FROM orders;

Introduction to Logical Operators

LIKE - This allows you to perform operations similar to using WHERE and =, but for cases when you might not know exactly what you are looking for. The LIKE operator is frequently used with %. The % tells us that we might want any number of characters leading up to a particular set of characters or following a certain set of characters.

/* select all the companies whose names start with 'C' */
SELECT name 
FROM accounts
WHERE name like 'C%'

/* select All companies whose names contain the string 'one' somewhere in the name. */
SELECT name
FROM accounts
WHERE name LIKE '%one%';

IN - This allows you to perform operations similar to using WHERE and =, but for more than one condition. We can check one, two or many column values for which we want to pull data, but all within the same query. TIP - In most SQL environments, you can use single or double quotation marks - and you may NEED to use double quotation marks if you have an apostrophe within the text you are attempting to pull.

SELECT name, primary_poc, sales_rep_id
FROM accounts
WHERE name IN ('Walmart', 'Target', 'Nordstrom');

NOT - This is used with IN and LIKE to select all of the rows NOT LIKE or NOT IN a certain condition.

/* Use the accounts table to find the account name, primary poc, and sales rep id for all stores except Walmart, Target, and Nordstrom */
SELECT name, primary_poc, sales_rep_id
FROM accounts
WHERE name NOT IN ('Walmart', 'Target', 'Nordstrom');

AND & BETWEEN - These allow you to combine operations where all combined conditions must be true. * The AND operator is used within a WHERE statement to consider more than one logical clause at a time. Each time you link a new statement with an AND, you will need to specify the column you are interested in looking at. * Sometimes we can make a cleaner statement using BETWEEN than we can using AND. Particularly this is true when we are using the same column for different parts of our AND statement. e.g.:

/*Instead of writing : WHERE column >= 6 AND column <= 10 we can instead write*/ 
WHERE column BETWEEN 6 AND 10
/* Write a query that returns all the orders where the standard_qty is over 1000, the poster_qty is 0, and the gloss_qty is 0 */
SELECT *
FROM orders
WHERE standard_qty > 1000 AND poster_qty = 0 AND gloss_qty = 0;

/* Using the accounts table find all the companies whose names do not start with 'C' and end with 's' */
SELECT name
FROM accounts
WHERE name NOT LIKE 'C%' AND name LIKE '%s';

/* Use the web_events table to find all information regarding individuals who were contacted via organic or adwords and started their account at any point in 2016 sorted from newest to oldest */ 
SELECT *
FROM web_events
WHERE channel IN ('organic', 'adwords') AND occurred_at BETWEEN '2016-01-01' AND '2017-01-01'
ORDER BY occurred_at DESC;
/*  You will notice that using BETWEEN is tricky for dates! While BETWEEN is generally inclusive of endpoints, it assumes the time is at 00:00:00 (i.e. midnight) for dates. This is the reason why we set the right-side endpoint of the period at '2017-01-01'. */

OR - This allow you to combine operations where at least one of the combined conditions must be true. Similar to the AND operator, the OR operator can combine multiple statements. Each time you link a new statement with an OR, you will need to specify the column you are interested in looking at.

/* Find list of orders ids where either gloss_qty or poster_qty is greater than 4000. Only include the id field in the resulting table. */
SELECT id
FROM orders
WHERE gloss_qty > 4000 OR poster_qty > 4000;

/* Write a query that returns a list of orders where the standard_qty is zero and either the gloss_qty or poster_qty is over 1000. */
SELECT *
FROM orders
WHERE standard_qty = 0 AND (gloss_qty > 1000 OR poster_qty > 1000);

/* Find all the company names that start with a 'C' or 'W', and the primary contact contains 'ana' or 'Ana', but it doesn't contain 'eana'*/
SELECT *
FROM accounts
WHERE (name LIKE 'C%' OR name LIKE 'W%') 
           AND ((primary_poc LIKE '%ana%' OR primary_poc LIKE '%Ana%') 
           AND primary_poc NOT LIKE '%eana%');

Summary of basic comands:


JOINS

Inner joins

Only return rows that appear in both tables e.g. Using ERD: Pull standard_qty, gloss_qty, and poster_qty from the orders table, and the website and the primary_poc from the accounts table.

SELECT orders.standard_qty, orders.gloss_qty, 
       orders.poster_qty,  accounts.website, 
       accounts.primary_poc
FROM orders
JOIN accounts
ON orders.account_id = accounts.id

Notice that we need to specify every table a column comes from in the SELECT statement. ### ALIAS When we JOIN tables together, it is nice to give each table an alias. Frequently an alias is just the first letter of the table name.

FROM tablename t1 
JOIN tablename2 t2

Questions

  1. Provide a table for all web_events associated with account name of Walmart. There should be three columns. Be sure to include the primary_poc, time of the event, and the channel for each event. Additionally, you might choose to add a fourth column to assure only Walmart events were chosen.
SELECT a.primary_poc, w.occurred_at, w.channel, a.name
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
WHERE a.name = 'Walmart';
  1. Provide a table that provides the region for each sales_rep along with their associated accounts. Your final table should include three columns: the region name, the sales rep name, and the account name. Sort the accounts alphabetically (A-Z) according to account name.
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
ORDER BY a.name;
  1. Provide the name for each region for every order, as well as the account name and the unit price they paid (total_amt_usd/total) for the order. Your final table should have 3 columns: region name, account name, and unit price. A few accounts have 0 for total, so divide by (total + 0.01) to assure not dividing by zero.
SELECT r.name region, a.name account, 
       o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id;

Outer joins

If we want to include data that only exists in one of the tables we want to join we can use an outer join If there is not matching information in the JOINed table, then you will have columns with empty cells with a data type called NULL. Writing a LEFT join statement: Comparing LEFT and RIGHT join: LEFT and RIGHT joins are essentially inter changeable so its standard to just use LEFT joins.

JOINS and filtering

If you wanted to filter your results so that it only showed sales from a particular sales rep you could use WHERE but it’s better to use AND as part of the ON statement. A simple rule to remember this is that, when the database executes this query, it executes the join and everything in the ON clause first. Think of this as building the new result set. That result set is then filtered using the WHERE clause.

SELECT orders.*, 
accounts.*
FROM orders
LEFT JOIN accounts
ON orders.account_id = accounts.id
/* Use AND in stead of WHERE for filtering this LEFT JOIN */
AND accounts.sales_rep_id = 325 

Questions

  1. Provide a table that provides the region for each sales_rep along with their associated accounts. This time only for the Midwest region. Your final table should include three columns: the region name, the sales rep name, and the account name. Sort the accounts alphabetically (A-Z) according to account name.
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest'
ORDER BY a.name;
  1. Provide a table that provides the region for each sales_rep along with their associated accounts. This time only for accounts where the sales rep has a first name starting with S and in the Midwest region. Your final table should include three columns: the region name, the sales rep name, and the account name. Sort the accounts alphabetically (A-Z) according to account name.
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest' AND s.name LIKE 'S%'
ORDER BY a.name;
  1. Provide a table that provides the region for each sales_rep along with their associated accounts. This time only for accounts where the sales rep has a last name starting with K and in the Midwest region. Your final table should include three columns: the region name, the sales rep name, and the account name. Sort the accounts alphabetically (A-Z) according to account name.
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest' AND s.name LIKE '% K%'
ORDER BY a.name;
  1. Provide the name for each region for every order, as well as the account name and the unit price they paid (total_amt_usd/total) for the order. However, you should only provide the results if the standard order quantity exceeds 100. Your final table should have 3 columns: region name, account name, and unit price. In order to avoid a division by zero error, adding .01 to the denominator here is helpful total_amt_usd/(total+0.01).
SELECT r.name region, a.name account, o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
WHERE o.standard_qty > 100;
  1. Provide the name for each region for every order, as well as the account name and the unit price they paid (total_amt_usd/total) for the order. However, you should only provide the results if the standard order quantity exceeds 100 and the poster order quantity exceeds 50. Your final table should have 3 columns: region name, account name, and unit price. Sort for the smallest unit price first. In order to avoid a division by zero error, adding .01 to the denominator here is helpful (total_amt_usd/(total+0.01).
SELECT r.name region, a.name account, o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
WHERE o.standard_qty > 100 AND o.poster_qty > 50
ORDER BY unit_price;
  1. Provide the name for each region for every order, as well as the account name and the unit price they paid (total_amt_usd/total) for the order. However, you should only provide the results if the standard order quantity exceeds 100 and the poster order quantity exceeds 50. Your final table should have 3 columns: region name, account name, and unit price. Sort for the largest unit price first. In order to avoid a division by zero error, adding .01 to the denominator here is helpful (total_amt_usd/(total+0.01).
SELECT r.name region, a.name account, o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
WHERE o.standard_qty > 100 AND o.poster_qty > 50
ORDER BY unit_price DESC;
  1. What are the different channels used by account id 1001? Your final table should have only 2 columns: account name and the different channels. You can try SELECT DISTINCT to narrow down the results to only the unique values.
SELECT DISTINCT a.name, w.channel
FROM accounts a
RIGHT JOIN web_events w
ON a.id = w.account_id
WHERE a.id = '1001';
  1. Find all the orders that occurred in 2015. Your final table should have 4 columns: occurred_at, account name, order total, and order total_amt_usd.
SELECT o.occurred_at, a.name, o.total, o.total_amt_usd
FROM accounts a
JOIN orders o
ON o.account_id = a.id
WHERE o.occurred_at BETWEEN '01-01-2015' AND '01-01-2016'
ORDER BY o.occurred_at DESC;

AGGREGATIONS

Null values

When identifying NULLs in a WHERE clause, we write IS NULL or IS NOT NULL. We don’t use =, because NULL isn’t considered a value in SQL.

COUNT

Count the number of rows in a table

SELECT COUNT(*)
FROM accounts;

COUNT does not consider rows that have NULL values. Therefore, this can be useful for quickly identifying which rows have missing data.

SUM

Unlike COUNT, you can only use SUM on numeric columns. However, SUM will ignore NULL values and treat them as 0.

Questions

  1. Find the total amount of poster_qty paper ordered in the orders table.
SELECT SUM(poster_qty) AS total_poster_sales
FROM orders;
  1. Find the total amount of standard_qty paper ordered in the orders table.
SELECT SUM(standard_qty) AS total_standard_sales
FROM orders;
  1. Find the total dollar amount of sales using the total_amt_usd in the orders table.
SELECT SUM(total_amt_usd) AS total_dollar_sales
FROM orders;
  1. Find the total amount spent on standard_amt_usd and gloss_amt_usd paper for each order in the orders table. This should give a dollar amount for each order in the table. Notice, this solution did not use an aggregate.
SELECT standard_amt_usd + gloss_amt_usd AS total_standard_gloss
FROM orders;
  1. Find the standard_amt_usd per unit of standard_qty paper. Your solution should use both an aggregation and a mathematical operator. Notice, this solution used both an aggregate and our mathematical operators
SELECT SUM(standard_amt_usd)/SUM(standard_qty) AS standard_price_per_unit
FROM orders;

MIN and MAX

MIN and MAX are aggregators that ignore NULL values. Functionally, MIN and MAX are similar to COUNT in that they can be used on non-numerical columns. Depending on the column type, MIN will return the lowest number, earliest date, or non-numerical value as early in the alphabet as possible. As you might suspect, MAX does the opposite—it returns the highest number, the latest date, or the non-numerical value closest alphabetically to “Z.”

Questions

  1. When was the earliest order ever placed?
SELECT MIN(occurred_at) 
FROM orders;

Try performing the same query as in question 1 without using an aggregation function.

SELECT occurred_at 
FROM orders 
ORDER BY occurred_at
LIMIT 1;
  1. When did the most recent (latest) web_event occur?
SELECT MAX(occurred_at)
FROM web_events;

Try to perform the result of the previous query without using an aggregation function.

SELECT occurred_at
FROM web_events
ORDER BY occurred_at DESC
LIMIT 1;

AVG

AVG returns the mean of the data - that is the sum of all of the values in the column divided by the number of values in a column. This aggregate function ignores the NULL values in both the numerator and the denominator.

Questions

  1. Find the mean (AVERAGE) amount spent per order on each paper type, as well as the mean amount of each paper type purchased per order. Your final answer should have 6 values - one for each paper type for the average number of sales, as well as the average amount.
SELECT AVG(standard_qty) mean_standard, AVG(gloss_qty) mean_gloss, 
           AVG(poster_qty) mean_poster, AVG(standard_amt_usd) mean_standard_usd, 
           AVG(gloss_amt_usd) mean_gloss_usd, AVG(poster_amt_usd) mean_poster_usd
FROM orders;
  1. What is the MEDIAN total_usd spent on all orders? Note, this is more advanced than the topics we have covered thus far to build a general solution, but we can hard code a solution in the following way.
SELECT *
FROM (SELECT total_amt_usd
      FROM orders
      ORDER BY total_amt_usd
      LIMIT 3457) AS Table1
ORDER BY total_amt_usd DESC
LIMIT 2;

Since there are 6912 orders - we want the average of the 3457 and 3456 order amounts when ordered. This is the average of 2483.16 and 2482.55. This gives the median of 2482.855. This obviously isn’t an ideal way to compute. If we obtain new orders, we would have to change the limit. SQL didn’t even calculate the median for us. The above used a SUBQUERY, but you could use any method to find the two necessary values, and then you just need the average of them.

GROUP BY

GROUP BY can be used to aggregate data within subsets of the data. For example, grouping for different accounts, different regions, or different sales representatives. Any column in the SELECT statement that is not within an aggregator must be in the GROUP BY clause. The GROUP BY always goes between WHERE and ORDER BY. ORDER BY works like SORT in spreadsheet software. SQL evaluates the aggregations before the LIMIT clause. If you don’t group by any columns, you’ll get a 1-row result—no problem there. If you group by a column with enough unique values that it exceeds the LIMIT number, the aggregates will be calculated, and then some rows will simply be omitted from the results. This is a nice way to do things because you know you’re going to get the correct aggregates. If SQL cuts the table down to 100 rows, then performed the aggregations, your results would be substantially different.

Note: You can reference the columns in your select statement in GROUP BY and ORDER BY clauses with numbers that follow the order they appear in the select statement. For example

SELECT standard_qty, COUNT(*)

FROM orders

GROUP BY 1 /*(this 1 refers to standard_qty since it is the first of the columns included in the select statement)*/

ORDER BY 1 /*(this 1 refers to standard_qty since it is the first of the columns included in the select statement)*/

Questions

  1. Which account (by name) placed the earliest order? Your solution should have the account name and the date of the order.
SELECT a.name, o.occurred_at
FROM accounts a
JOIN orders o
ON a.id = o.account_id
ORDER BY occurred_at
LIMIT 1;
  1. Find the total sales in usd for each account. You should include two columns - the total sales for each company’s orders in usd and the company name.
SELECT a.name, SUM(total_amt_usd) total_sales
FROM orders o
JOIN accounts a
ON a.id = o.account_id
GROUP BY a.name;
  1. Via what channel did the most recent (latest) web_event occur, which account was associated with this web_event? Your query should return only three values - the date, channel, and account name.
SELECT w.occurred_at, w.channel, a.name
FROM web_events w
JOIN accounts a
ON w.account_id = a.id 
ORDER BY w.occurred_at DESC
LIMIT 1;
  1. Find the total number of times each type of channel from the web_events was used. Your final table should have two columns - the channel and the number of times the channel was used.
SELECT w.channel, COUNT(*)
FROM web_events w
GROUP BY w.channel
  1. Who was the primary contact associated with the earliest web_event?
SELECT a.primary_poc
FROM web_events w
JOIN accounts a
ON a.id = w.account_id
ORDER BY w.occurred_at
LIMIT 1;
  1. What was the smallest order placed by each account in terms of total usd. Provide only two columns - the account name and the total usd. Order from smallest dollar amounts to largest.
SELECT a.name, MIN(total_amt_usd) smallest_order
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.name
ORDER BY smallest_order;

Sort of strange we have a bunch of orders with no dollars. We might want to look into those.

  1. Find the number of sales reps in each region. Your final table should have two columns - the region and the number of sales_reps. Order from fewest reps to most reps.
SELECT r.name, COUNT(*) num_reps
FROM region r
JOIN sales_reps s
ON r.id = s.region_id
GROUP BY r.name
ORDER BY num_reps;

You can GROUP BY multiple columns at once. This is often useful to aggregate across a number of different segments. The order of columns listed in the ORDER BY clause does make a difference. You are ordering the columns from left to right. The order of column names in your GROUP BY clause doesn’t matter—the results will be the same regardless.

Questions

  1. For each account, determine the average amount of each type of paper they purchased across their orders. Your result should have four columns - one for the account name and one for the average spent on each of the paper types.
SELECT a.name, AVG(o.standard_qty) avg_stand, AVG(o.gloss_qty) avg_gloss, AVG(o.poster_qty) avg_post
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.name;
  1. For each account, determine the average amount spent per order on each paper type. Your result should have four columns - one for the account name and one for the average amount spent on each paper type.
SELECT a.name, AVG(o.standard_amt_usd) avg_stand, AVG(o.gloss_amt_usd) avg_gloss, AVG(o.poster_amt_usd) avg_post
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.name;
  1. Determine the number of times a particular channel was used in the web_events table for each sales rep. Your final table should have three columns - the name of the sales rep, the channel, and the number of occurrences. Order your table with the highest number of occurrences first.
SELECT s.name, w.channel, COUNT(*) num_events
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.name, w.channel
ORDER BY num_events DESC;
  1. Determine the number of times a particular channel was used in the web_events table for each region. Your final table should have three columns - the region name, the channel, and the number of occurrences. Order your table with the highest number of occurrences first.
SELECT r.name, w.channel, COUNT(*) num_events
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
JOIN sales_reps s
ON s.id = a.sales_rep_id
JOIN region r
ON r.id = s.region_id
GROUP BY r.name, w.channel
ORDER BY num_events DESC;

DISTINCT

DISTINCT is always used in SELECT statements, and it provides the unique rows for all columns written in the SELECT statement. Therefore, you only use DISTINCT once in any particular SELECT statement.

SELECT DISTINCT column1, column2, column3
FROM table1;

which would return the unique (or DISTINCT) rows across all three columns. Note: using DISTINCT, particularly in aggregations, can slow your queries down quite a bit.

Questions

  1. Use DISTINCT to test if there are any accounts associated with more than one region.

The below two queries have the same number of resulting rows (351), so we know that every account is associated with only one region. If each account was associated with more than one region, the first query should have returned more rows than the second query.

SELECT a.id as "account id", r.id as "region id", 
a.name as "account name", r.name as "region name"
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
JOIN region r
ON r.id = s.region_id;

and

SELECT DISTINCT id, name
FROM accounts;
  1. Have any sales reps worked on more than one account?

Actually all of the sales reps have worked on more than one account. The fewest number of accounts any sales rep works on is 3. There are 50 sales reps, and they all have more than one account. Using DISTINCT in the second query assures that all of the sales reps are accounted for in the first query.

SELECT s.id, s.name, COUNT(*) num_accounts
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.id, s.name
ORDER BY num_accounts;
SELECT DISTINCT id, name
FROM sales_reps;

HAVING

HAVING is the “clean” way to filter a query that has been aggregated, but this is also commonly done using a subquery. Essentially, any time you want to perform a WHERE on an element of your query that was created by an aggregate, you need to use HAVING instead. HAVING appears after the GROUP BY clause but before the ORDER BY clause.

Questions

  1. How many of the sales reps have more than 5 accounts that they manage?
SELECT s.id, s.name, COUNT(*) num_accounts
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.id, s.name
HAVING COUNT(*) > 5
ORDER BY num_accounts;
  1. How many accounts have more than 20 orders?
SELECT a.id, a.name, COUNT(*) num_orders
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
HAVING COUNT(*) > 20
ORDER BY num_orders;
  1. Which account has the most orders?
SELECT a.id, a.name, COUNT(*) num_orders
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
ORDER BY num_orders DESC
LIMIT 1;
  1. How many accounts spent more than 30,000 usd total across all orders?
SELECT a.id, a.name, SUM(o.total_amt_usd) total_spent
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
HAVING SUM(o.total_amt_usd) > 30000
ORDER BY total_spent;
  1. How many accounts spent less than 1,000 usd total across all orders?
SELECT a.id, a.name, SUM(o.total_amt_usd) total_spent
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
HAVING SUM(o.total_amt_usd) < 1000
ORDER BY total_spent;
  1. Which account has spent the most with us?
SELECT a.id, a.name, SUM(o.total_amt_usd) total_spent
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
ORDER BY total_spent DESC
LIMIT 1;
  1. Which account has spent the least with us?
SELECT a.id, a.name, SUM(o.total_amt_usd) total_spent
FROM accounts a
JOIN orders o
ON a.id = o.account_id
GROUP BY a.id, a.name
ORDER BY total_spent
LIMIT 1;
  1. Which accounts used facebook as a channel to contact customers more than 6 times?
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
GROUP BY a.id, a.name, w.channel
HAVING COUNT(*) > 6 AND w.channel = 'facebook'
ORDER BY use_of_channel;
  1. Which account used facebook most as a channel?
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
WHERE w.channel = 'facebook'
GROUP BY a.id, a.name, w.channel
ORDER BY use_of_channel DESC
LIMIT 1;
  1. Which channel was most frequently used by most accounts?
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
GROUP BY a.id, a.name, w.channel
ORDER BY use_of_channel DESC
LIMIT 10;

DATE Functions

GROUPing BY a date column is not usually very useful in SQL, as these columns tend to have transaction data down to a second. Dates are stored in year, month, day, hour, minute, second, which helps us in truncating. There are a number of built in SQL functions that are aimed at helping us improve our experience in working with dates: In SQL dates are stored in the format YYYY-MM-DD with the time in hours, minutes and seconds at the end. DATE_TRUNC allows you to truncate your date to a particular part of your date-time column. Common trunctions are day, month, and year. DATE_PART can be useful for pulling a specific portion of a date, but notice pulling month or day of the week (dow) means that you are no longer keeping the years in order. Rather you are grouping for certain components regardless of which year they belonged in. Note: DOW (day of week) returns a value from 0-6 where 0 is Sunday and 6 is Saturday.

Questions

  1. Find the sales in terms of total dollars for all orders in each year, ordered from greatest to least. Do you notice any trends in the yearly sales totals?
SELECT DATE_PART('year', occurred_at) ord_year,  SUM(total_amt_usd) total_spent
FROM orders
GROUP BY 1
ORDER BY 2 DESC;

When we look at the yearly totals, you might notice that 2013 and 2017 have much smaller totals than all other years. If we look further at the monthly data, we see that for 2013 and 2017 there is only one month of sales for each of these years (12 for 2013 and 1 for 2017). Therefore, neither of these are evenly represented. Sales have been increasing year over year, with 2016 being the largest sales to date. At this rate, we might expect 2017 to have the largest sales.

  1. Which month did Parch & Posey have the greatest sales in terms of total dollars? Are all months evenly represented by the dataset?

In order for this to be ‘fair’, we should remove the sales from 2013 and 2017. For the same reasons as discussed above.

SELECT DATE_PART('month', occurred_at) ord_month, SUM(total_amt_usd) total_spent
FROM orders
WHERE occurred_at BETWEEN '2014-01-01' AND '2017-01-01'
GROUP BY 1
ORDER BY 2 DESC;

The greatest sales amounts occur in December (12).

  1. Which year did Parch & Posey have the greatest sales in terms of total number of orders? Are all years evenly represented by the dataset?
SELECT DATE_PART('year', occurred_at) ord_year,  COUNT(*) total_sales
FROM orders
GROUP BY 1
ORDER BY 2 DESC;

Again, 2016 by far has the most amount of orders, but again 2013 and 2017 are not evenly represented to the other years in the dataset.

  1. Which month did Parch & Posey have the greatest sales in terms of total number of orders? Are all months evenly represented by the dataset?
SELECT DATE_PART('month', occurred_at) ord_month, COUNT(*) total_sales
FROM orders
WHERE occurred_at BETWEEN '2014-01-01' AND '2017-01-01'
GROUP BY 1
ORDER BY 2 DESC;

December still has the most sales, but interestingly, November has the second most sales (but not the most dollar sales. To make a fair comparison from one month to another 2017 and 2013 data were removed.

  1. In which month of which year did Walmart spend the most on gloss paper in terms of dollars?
SELECT DATE_TRUNC('month', o.occurred_at) ord_date, SUM(o.gloss_amt_usd) tot_spent
FROM orders o 
JOIN accounts a
ON a.id = o.account_id
WHERE a.name = 'Walmart'
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1;

May 2016 was when Walmart spent the most on gloss paper.

CASE

The CASE statement always goes in the SELECT clause. CASE must include the following components: WHEN, THEN, and END. ELSE is an optional component to catch cases that didn’t meet any of the other previous CASE conditions. You can make any conditional statement using any conditional operator (like WHERE) between WHEN and THEN. This includes stringing together multiple conditional statements using AND and OR. You can include multiple WHEN statements, as well as an ELSE statement again, to deal with any unaddressed conditions. ### Example In a quiz question in the previous Basic SQL lesson, you saw this question: 1. Create a column that divides the standard_amt_usd by the standard_qty to find the unit price for standard paper for each order. Limit the results to the first 10 orders, and include the id and account_id fields. NOTE - you will be thrown an error with the correct solution to this question. This is for a division by zero. You will learn how to get a solution without an error to this query when you learn about CASE statements in a later section. Let’s see how we can use the CASE statement to get around this error.

SELECT id, account_id, standard_amt_usd/standard_qty AS unit_price
FROM orders
LIMIT 10;

Now, let’s use a CASE statement. This way any time the standard_qty is zero, we will return 0, and otherwise we will return the unit_price.

SELECT account_id, CASE WHEN standard_qty = 0 OR standard_qty IS NULL THEN 0
                        ELSE standard_amt_usd/standard_qty END AS unit_price
FROM orders
LIMIT 10;

Now the first part of the statement will catch any of those division by zero values that were causing the error, and the other components will compute the division as necessary. You will notice, we essentially charge all of our accounts 4.99 for standard paper. It makes sense this doesn’t fluctuate, and it is more accurate than adding 1 in the denominator like our quick fix might have been in the earlier lesson.

Questions

  1. We would like to understand 3 different levels of customers based on the amount associated with their purchases. The top branch includes anyone with a Lifetime Value (total sales of all orders) greater than 200,000 usd. The second branch is between 200,000 and 100,000 usd. The lowest branch is anyone under 100,000 usd. Provide a table that includes the level associated with each account. You should provide the account name, the total sales of all orders for the customer, and the level. Order with the top spending customers listed first.
SELECT a.name, SUM(total_amt_usd) total_spent, 
     CASE WHEN SUM(total_amt_usd) > 200000 THEN 'top'
     WHEN  SUM(total_amt_usd) > 100000 THEN 'middle'
     ELSE 'low' END AS customer_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id 
GROUP BY a.name
ORDER BY 2 DESC;
  1. We would now like to perform a similar calculation to the first, but we want to obtain the total amount spent by customers only in 2016 and 2017. Keep the same levels as in the previous question. Order with the top spending customers listed first.
SELECT a.name, SUM(total_amt_usd) total_spent, 
     CASE WHEN SUM(total_amt_usd) > 200000 THEN 'top'
     WHEN  SUM(total_amt_usd) > 100000 THEN 'middle'
     ELSE 'low' END AS customer_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id
WHERE occurred_at > '2015-12-31' 
GROUP BY 1
ORDER BY 2 DESC;
  1. We would like to identify top performing sales reps, which are sales reps associated with more than 200 orders. Create a table with the sales rep name, the total number of orders, and a column with top or not depending on if they have more than 200 orders. Place the top sales people first in your final table.
SELECT s.name, COUNT(*) num_ords,
     CASE WHEN COUNT(*) > 200 THEN 'top'
     ELSE 'not' END AS sales_rep_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id 
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.name
ORDER BY 2 DESC;

It is worth mentioning that this assumes each name is unique - which has been done a few times. We otherwise would want to break by the name and the id of the table.

  1. The previous didn’t account for the middle, nor the dollar amount associated with the sales. Management decides they want to see these characteristics represented as well. We would like to identify top performing sales reps, which are sales reps associated with more than 200 orders or more than 750000 in total sales. The middle group has any rep with more than 150 orders or 500000 in sales. Create a table with the sales rep name, the total number of orders, total sales across all orders, and a column with top, middle, or low depending on this criteria. Place the top sales people based on dollar amount of sales first in your final table. You might see a few upset sales people by this criteria!
SELECT s.name, COUNT(*), SUM(o.total_amt_usd) total_spent, 
     CASE WHEN COUNT(*) > 200 OR SUM(o.total_amt_usd) > 750000 THEN 'top'
     WHEN COUNT(*) > 150 OR SUM(o.total_amt_usd) > 500000 THEN 'middle'
     ELSE 'low' END AS sales_rep_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id 
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.name
ORDER BY 3 DESC;

Subqueries

Whenever we need to use existing tables to create a new table that we then want to query again, this is an indication that we will need to use some sort of subquery.

e.g.Find the average number of events that occur for each day for each channel: (i) First, we needed to group by the day and channel. Then ordering by the number of events (the third column) gave us a quick way to answer the first question.

SELECT DATE_TRUNC('day',occurred_at) AS day,
   channel, COUNT(*) as events
FROM web_events
GROUP BY 1,2
ORDER BY 3 DESC;

(ii)Here you can see that to get the entire table in question 1 back, we included an * in our SELECT statement. You will need to be sure to alias your table.

SELECT *
FROM (SELECT DATE_TRUNC('day',occurred_at) AS day,
           channel, COUNT(*) as events
     FROM web_events 
     GROUP BY 1,2
     ORDER BY 3 DESC) sub;
  1. Finally, here we are able to get a table that shows the average number of events a day for each channel.. Since you broke out by day in inner query, this is giving you an average per day.
SELECT channel, AVG(events) AS average_events
FROM (SELECT DATE_TRUNC('day',occurred_at) AS day,
             channel, COUNT(*) as events
      FROM web_events 
      GROUP BY 1,2) sub
GROUP BY channel
ORDER BY 2 DESC;

Format subqueries using indents in order to make it easier to read

Subqueries Part II

In the first subquery you wrote, you created a table that you could then query again in the FROM statement. However, if you are only returning a single value, you might use that value in a logical statement like WHERE, HAVING, or even SELECT - the value could be nested within a CASE statement. It can be used anyehwre you might have a table name, or a column name or individual value. Note that you should not include an alias when you write a subquery in a conditional statement. This is because the subquery is treated as an individual value (or set of values in the IN case) rather than as a table.

e.g. Return the average quantity of each type of paper that was sold in the same moth as Parch & Poseys first order ever. (i)To get the date of the first order you could write a subquery with a min function.

SELECT DATE_TRUNC('month', MIN(occurred_at)) 
FROM orders;

(ii)Then to pull the average for each, we could do this all in one query, but for readability, I provided two queries below to perform each separately.

SELECT AVG(standard_qty) avg_std, AVG(gloss_qty) avg_gls, AVG(poster_qty) avg_pst
FROM orders
WHERE DATE_TRUNC('month', occurred_at) = 
     (SELECT DATE_TRUNC('month', MIN(occurred_at)) FROM orders);

SELECT SUM(total_amt_usd)
FROM orders
WHERE DATE_TRUNC('month', occurred_at) = 
      (SELECT DATE_TRUNC('month', MIN(occurred_at)) FROM orders);

Also, notice the query here compared a single value. Most conditional logic will work with subqueries containing one cell results. IN is the only type of conditional logic that will work when the inner query contains multiple results. If we returned an entire column IN would need to be used to perform a logical argument. If we are returning an entire table, then we must use an ALIAS for the table, and perform additional logic on the entire table.

Questions

  1. Provide the name of the sales_rep in each region with the largest amount of total_amt_usd sales. First, I wanted to find the total_amt_usd totals associated with each sales rep, and I also wanted the region in which they were located. The query below provided this information.
SELECT s.name rep_name, r.name region_name, SUM(o.total_amt_usd) total_amt
FROM sales_reps s
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
JOIN region r
ON r.id = s.region_id
GROUP BY 1,2
ORDER BY 3 DESC;

Next, I pulled the max for each region, and then we can use this to pull those rows in our final result.

SELECT region_name, MAX(total_amt) total_amt
     FROM(SELECT s.name rep_name, r.name region_name, SUM(o.total_amt_usd) total_amt
             FROM sales_reps s
             JOIN accounts a
             ON a.sales_rep_id = s.id
             JOIN orders o
             ON o.account_id = a.id
             JOIN region r
             ON r.id = s.region_id
             GROUP BY 1, 2) t1
     GROUP BY 1;

Essentially, this is a JOIN of these two tables, where the region and amount match.

SELECT t3.rep_name, t3.region_name, t3.total_amt
FROM(SELECT region_name, MAX(total_amt) total_amt
     FROM(SELECT s.name rep_name, r.name region_name, SUM(o.total_amt_usd) total_amt
             FROM sales_reps s
             JOIN accounts a
             ON a.sales_rep_id = s.id
             JOIN orders o
             ON o.account_id = a.id
             JOIN region r
             ON r.id = s.region_id
             GROUP BY 1, 2) t1
     GROUP BY 1) t2
JOIN (SELECT s.name rep_name, r.name region_name, SUM(o.total_amt_usd) total_amt
     FROM sales_reps s
     JOIN accounts a
     ON a.sales_rep_id = s.id
     JOIN orders o
     ON o.account_id = a.id
     JOIN region r
     ON r.id = s.region_id
     GROUP BY 1,2
     ORDER BY 3 DESC) t3
ON t3.region_name = t2.region_name AND t3.total_amt = t2.total_amt;

2.For the region with the largest sales total_amt_usd, how many total orders were placed? The first query I wrote was to pull the total_amt_usd for each region.

SELECT r.name region_name, SUM(o.total_amt_usd) total_amt
FROM sales_reps s
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
JOIN region r
ON r.id = s.region_id
GROUP BY r.name;

Then we just want the region with the max amount from this table. There are two ways I considered getting this amount. One was to pull the max using a subquery. Another way is to order descending and just pull the top value.

SELECT MAX(total_amt)
FROM (SELECT r.name region_name, SUM(o.total_amt_usd) total_amt
             FROM sales_reps s
             JOIN accounts a
             ON a.sales_rep_id = s.id
             JOIN orders o
             ON o.account_id = a.id
             JOIN region r
             ON r.id = s.region_id
             GROUP BY r.name) sub;

Finally, we want to pull the total orders for the region with this amount:

SELECT r.name, SUM(o.total) total_orders
FROM sales_reps s
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
JOIN region r
ON r.id = s.region_id
GROUP BY r.name
HAVING SUM(o.total_amt_usd) = (
      SELECT MAX(total_amt)
      FROM (SELECT r.name region_name, SUM(o.total_amt_usd) total_amt
              FROM sales_reps s
              JOIN accounts a
              ON a.sales_rep_id = s.id
              JOIN orders o
              ON o.account_id = a.id
              JOIN region r
              ON r.id = s.region_id
              GROUP BY r.name) sub);
  1. For the name of the account that purchased the most (in total over their lifetime as a customer) standard_qty paper, how many accounts still had more in total purchases? First, we want to find the account that had the most standard_qty paper. The query here pulls that account, as well as the total amount:
SELECT a.name account_name, SUM(o.standard_qty) total_std, SUM(o.total) total
FROM accounts a
JOIN orders o
ON o.account_id = a.id
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1;

Now, I want to use this to pull all the accounts with more total sales:

SELECT a.name
FROM orders o
JOIN accounts a
ON a.id = o.account_id
GROUP BY 1
HAVING SUM(o.total) > (SELECT total 
                  FROM (SELECT a.name act_name, SUM(o.standard_qty) tot_std, SUM(o.total) total
                        FROM accounts a
                        JOIN orders o
                        ON o.account_id = a.id
                        GROUP BY 1
                        ORDER BY 2 DESC
                        LIMIT 1) sub);

This is now a list of all the accounts with more total orders. We can get the count with just another simple subquery.

SELECT COUNT(*)
FROM (SELECT a.name
      FROM orders o
      JOIN accounts a
      ON a.id = o.account_id
      GROUP BY 1
      HAVING SUM(o.total) > (SELECT total 
                  FROM (SELECT a.name act_name, SUM(o.standard_qty) tot_std, SUM(o.total) total
                        FROM accounts a
                        JOIN orders o
                        ON o.account_id = a.id
                        GROUP BY 1
                        ORDER BY 2 DESC
                        LIMIT 1) inner_tab)
            ) counter_tab;
  1. For the customer that spent the most (in total over their lifetime as a customer) total_amt_usd, how many web_events did they have for each channel? Here, we first want to pull the customer with the most spent in lifetime value.
SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
FROM orders o
JOIN accounts a
ON a.id = o.account_id
GROUP BY a.id, a.name
ORDER BY 3 DESC
LIMIT 1;

Now, we want to look at the number of events on each channel this company had, which we can match with just the id.

SELECT a.name, w.channel, COUNT(*)
FROM accounts a
JOIN web_events w
ON a.id = w.account_id AND a.id =  (SELECT id
                     FROM (SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
                           FROM orders o
                           JOIN accounts a
                           ON a.id = o.account_id
                           GROUP BY a.id, a.name
                           ORDER BY 3 DESC
                           LIMIT 1) inner_table)
GROUP BY 1, 2
ORDER BY 3 DESC;

I added an ORDER BY for no real reason, and the account name to assure I was only pulling from one account. 5. What is the lifetime average amount spent in terms of total_amt_usd for the top 10 total spending accounts? First, we just want to find the top 10 accounts in terms of highest total_amt_usd.

SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
FROM orders o
JOIN accounts a
ON a.id = o.account_id
GROUP BY a.id, a.name
ORDER BY 3 DESC
LIMIT 10;

Now, we just want the average of these 10 amounts.

SELECT AVG(tot_spent)
FROM (SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
      FROM orders o
      JOIN accounts a
      ON a.id = o.account_id
      GROUP BY a.id, a.name
      ORDER BY 3 DESC
       LIMIT 10) temp;
  1. What is the lifetime average amount spent in terms of total_amt_usd for only the companies that spent more than the average of all orders. First, we want to pull the average of all accounts in terms of total_amt_usd:
SELECT AVG(o.total_amt_usd) avg_all
FROM orders o
JOIN accounts a
ON a.id = o.account_id;

Then, we want to only pull the accounts with more than this average amount.

SELECT o.account_id, AVG(o.total_amt_usd)
FROM orders o
GROUP BY 1
HAVING AVG(o.total_amt_usd) > (SELECT AVG(o.total_amt_usd) avg_all
                               FROM orders o
                               JOIN accounts a
                               ON a.id = o.account_id);

Finally, we just want the average of these values.

SELECT AVG(avg_amt)
FROM (SELECT o.account_id, AVG(o.total_amt_usd) avg_amt
    FROM orders o
    GROUP BY 1
    HAVING AVG(o.total_amt_usd) > (SELECT AVG(o.total_amt_usd) avg_all
                               FROM orders o
                               JOIN accounts a
                               ON a.id = o.account_id)) temp_table;

WITH

The WITH statement is often called a Common Table Expression or CTE. Though these expressions serve the exact same purpose as subqueries, they are more common in practice, as they tend to be cleaner for a future reader to follow the logic. QUESTION: You need to find the average number of events for each channel per day Using subqueries:

SELECT channel, AVG(events) AS average_events
FROM (SELECT DATE_TRUNC('day',occurred_at) AS day,
             channel, COUNT(*) as events
      FROM web_events 
      GROUP BY 1,2) sub
GROUP BY channel
ORDER BY 2 DESC;

Let’s try this again using a WITH statement. Notice, you can pull the inner query:

SELECT DATE_TRUNC('day',occurred_at) AS day, 
       channel, COUNT(*) as events
FROM web_events 
GROUP BY 1,2

This is the part we put in the WITH statement. Notice, we are aliasing the table as events below:

WITH events AS (
          SELECT DATE_TRUNC('day',occurred_at) AS day, 
                        channel, COUNT(*) as events
          FROM web_events 
          GROUP BY 1,2)

Now, we can use this newly created events table as if it is any other table in our database:

WITH events AS (
          SELECT DATE_TRUNC('day',occurred_at) AS day, 
                        channel, COUNT(*) as events
          FROM web_events 
          GROUP BY 1,2)

SELECT channel, AVG(events) AS average_events
FROM events
GROUP BY channel
ORDER BY 2 DESC;

For the above example, we don’t need anymore than the one additional table, but imagine we needed to create a second table to pull from. We can create an additional table to pull from in the following way:

WITH table1 AS (
          SELECT *
          FROM web_events),

     table2 AS (
          SELECT *
          FROM accounts)


SELECT *
FROM table1
JOIN table2
ON table1.account_id = table2.id;

You can add more and more tables using the WITH statement in the same way.

Essentially a WITH statement performs the same task as a Subquery. Therefore, you can write any of the queries we worked with in the previous set of questions using a WITH. That’s what you’ll do here. Try to perform each of the earlier queries again, but using a WITH instead of a subquery.

Questions

  1. Provide the name of the sales_rep in each region with the largest amount of total_amt_usd sales.
WITH t1 AS (
  SELECT s.name rep_name, r.name region_name, SUM(o.total_amt_usd) total_amt
   FROM sales_reps s
   JOIN accounts a
   ON a.sales_rep_id = s.id
   JOIN orders o
   ON o.account_id = a.id
   JOIN region r
   ON r.id = s.region_id
   GROUP BY 1,2
   ORDER BY 3 DESC), 
t2 AS (
   SELECT region_name, MAX(total_amt) total_amt
   FROM t1
   GROUP BY 1)
SELECT t1.rep_name, t1.region_name, t1.total_amt
FROM t1
JOIN t2
ON t1.region_name = t2.region_name AND t1.total_amt = t2.total_amt;  

2.For the region with the largest sales total_amt_usd, how many total orders were placed?

WITH t1 AS (
   SELECT r.name region_name, SUM(o.total_amt_usd) total_amt
   FROM sales_reps s
   JOIN accounts a
   ON a.sales_rep_id = s.id
   JOIN orders o
   ON o.account_id = a.id
   JOIN region r
   ON r.id = s.region_id
   GROUP BY r.name), 
t2 AS (
   SELECT MAX(total_amt)
   FROM t1)
SELECT r.name, SUM(o.total) total_orders
FROM sales_reps s
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
JOIN region r
ON r.id = s.region_id
GROUP BY r.name
HAVING SUM(o.total_amt_usd) = (SELECT * FROM t2);
  1. For the name of the account that purchased the most (in total over their lifetime as a customer) standard_qty paper, how many accounts still had more in total purchases?
WITH t1 AS (
  SELECT a.name account_name, SUM(o.standard_qty) total_std, SUM(o.total) total
  FROM accounts a
  JOIN orders o
  ON o.account_id = a.id
  GROUP BY 1
  ORDER BY 2 DESC
  LIMIT 1), 
t2 AS (
  SELECT a.name
  FROM orders o
  JOIN accounts a
  ON a.id = o.account_id
  GROUP BY 1
  HAVING SUM(o.total) > (SELECT total FROM t1))
SELECT COUNT(*)
FROM t2;
  1. For the customer that spent the most (in total over their lifetime as a customer) total_amt_usd, how many web_events did they have for each channel?
WITH t1 AS (
   SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
   FROM orders o
   JOIN accounts a
   ON a.id = o.account_id
   GROUP BY a.id, a.name
   ORDER BY 3 DESC
   LIMIT 1)
SELECT a.name, w.channel, COUNT(*)
FROM accounts a
JOIN web_events w
ON a.id = w.account_id AND a.id =  (SELECT id FROM t1)
GROUP BY 1, 2
ORDER BY 3 DESC;
  1. What is the lifetime average amount spent in terms of total_amt_usd for the top 10 total spending accounts?
WITH t1 AS (
   SELECT a.id, a.name, SUM(o.total_amt_usd) tot_spent
   FROM orders o
   JOIN accounts a
   ON a.id = o.account_id
   GROUP BY a.id, a.name
   ORDER BY 3 DESC
   LIMIT 10)
SELECT AVG(tot_spent)
FROM t1;
  1. What is the lifetime average amount spent in terms of total_amt_usd for only the companies that spent more than the average of all orders.
WITH t1 AS (
   SELECT AVG(o.total_amt_usd) avg_all
   FROM orders o
   JOIN accounts a
   ON a.id = o.account_id),
t2 AS (
   SELECT o.account_id, AVG(o.total_amt_usd) avg_amt
   FROM orders o
   GROUP BY 1
   HAVING AVG(o.total_amt_usd) > (SELECT * FROM t1))
SELECT AVG(avg_amt)
FROM t2;

SQL Data Cleaning

LEFT, RIGHT, LENGTH

LEFT pulls a specified number of characters for each row in a specified column starting at the beginning (or from the left). For example, you can pull the first three digits of a phone number using LEFT(phone_number, 3).

RIGHT pulls a specified number of characters for each row in a specified column starting at the end (or from the right). For example, you can pull the last eight digits of a phone number using RIGHT(phone_number, 8).

LENGTH provides the number of characters for each row of a specified column. You could use this to get the length of each phone number as LENGTH(phone_number).

Questions

  1. In the accounts table, there is a column holding the website for each company. The last three digits specify what type of web address they are using. A list of extensions (and pricing) is provided. Pull these extensions and provide how many of each website type exist in the accounts table.
SELECT RIGHT(website, 3) AS domain, COUNT(*) num_companies
FROM accounts
GROUP BY 1
ORDER BY 2 DESC;
  1. There is much debate about how much the name (or even the first letter of a company name) matters. Use the accounts table to pull the first letter of each company name to see the distribution of company names that begin with each letter (or number).
SELECT LEFT(UPPER(name), 1) AS first_letter, COUNT(*) num_companies
FROM accounts
GROUP BY 1
ORDER BY 2 DESC;
  1. Use the accounts table and a CASE statement to create two groups: one group of company names that start with a number and a second group of those company names that start with a letter. What proportion of company names start with a letter?
SELECT SUM(num) nums, SUM(letter) letters
FROM (SELECT name, CASE WHEN LEFT(UPPER(name), 1) IN ('0','1','2','3','4','5','6','7','8','9') 
                       THEN 1 ELSE 0 END AS num, 
         CASE WHEN LEFT(UPPER(name), 1) IN ('0','1','2','3','4','5','6','7','8','9') 
                       THEN 0 ELSE 1 END AS letter
      FROM accounts) t1;

There are 350 company names that start with a letter and 1 that starts with a number. This gives a ratio of 350/351 that are company names that start with a letter or 99.7%.

  1. Consider vowels as a, e, i, o, and u. What proportion of company names start with a vowel, and what percent start with anything else?
SELECT SUM(vowels) vowels, SUM(other) other
FROM (SELECT name, CASE WHEN LEFT(UPPER(name), 1) IN ('A','E','I','O','U') 
                        THEN 1 ELSE 0 END AS vowels, 
          CASE WHEN LEFT(UPPER(name), 1) IN ('A','E','I','O','U') 
                       THEN 0 ELSE 1 END AS other
         FROM accounts) t1;

There are 80 company names that start with a vowel and 271 that start with other characters. Therefore 80/351 are vowels or 22.8%. Therefore, 77.2% of company names do not start with vowels.

POSITION, STRPOS, LOWER, UPPER

POSITION takes a character and a column, and provides the index where that character is for each row. The index of the first position is 1 in SQL. Here, you saw that you can pull the index of a comma as POSITION(‘,’ IN city_state).

STRPOS provides the same result as POSITION, but the syntax for achieving those results is a bit different as shown here: STRPOS(city_state, ‘,’).

Note, both POSITION and STRPOS are case sensitive, so looking for A is different than looking for a. Therefore, if you want to pull an index regardless of the case of a letter, you might want to use LOWER or UPPER to make all of the characters lower or uppercase.

Questions

  1. Use the accounts table to create first and last name columns that hold the first and last names for the primary_poc.
SELECT LEFT(primary_poc, STRPOS(primary_poc, ' ') -1 ) first_name, 
RIGHT(primary_poc, LENGTH(primary_poc) - STRPOS(primary_poc, ' ')) last_name
FROM accounts;
  1. Now see if you can do the same thing for every rep name in the sales_rep table. Again provide first and last name columns.
SELECT LEFT(name, STRPOS(name, ' ') -1 ) first_name, 
       RIGHT(name, LENGTH(name) - STRPOS(name, ' ')) last_name
FROM sales_reps;

CONCAT

Each of these will allow you to combine columns together across rows. For example, first and last names stored in separate columns could be combined together to create a full name: CONCAT(first_name, ‘’, last_name) or with piping as first_name || ‘’ || last_name.

Questions

  1. Each company in the accounts table wants to create an email address for each primary_poc. The email address should be the first name of the primary_poc . last name primary_poc @ company name .com.
WITH t1 AS (
 SELECT LEFT(primary_poc,     STRPOS(primary_poc, ' ') -1 ) first_name,  RIGHT(primary_poc, LENGTH(primary_poc) - STRPOS(primary_poc, ' ')) last_name, name
 FROM accounts)
SELECT first_name, last_name, CONCAT(first_name, '.', last_name, '@', name, '.com')
FROM t1;

2.You may have noticed that in the previous solution some of the company names include spaces, which will certainly not work in an email address. See if you can create an email address that will work by removing all of the spaces in the account name, but otherwise your solution should be just as in question 1. Some helpful documentation is here.

WITH t1 AS (
 SELECT LEFT(primary_poc,     STRPOS(primary_poc, ' ') -1 ) first_name,  RIGHT(primary_poc, LENGTH(primary_poc) - STRPOS(primary_poc, ' ')) last_name, name
 FROM accounts)
SELECT first_name, last_name, CONCAT(first_name, '.', last_name, '@', REPLACE(name, ' ', ''), '.com')
FROM  t1;

3.We would also like to create an initial password, which they will change after their first log in. The first password will be the first letter of the primary_poc’s first name (lowercase), then the last letter of their first name (lowercase), the first letter of their last name (lowercase), the last letter of their last name (lowercase), the number of letters in their first name, the number of letters in their last name, and then the name of the company they are working with, all capitalized with no spaces.

WITH t1 AS (
 SELECT LEFT(primary_poc,     STRPOS(primary_poc, ' ') -1 ) first_name,  RIGHT(primary_poc, LENGTH(primary_poc) - STRPOS(primary_poc, ' ')) last_name, name
 FROM accounts)
SELECT first_name, last_name, CONCAT(first_name, '.', last_name, '@', name, '.com'), LEFT(LOWER(first_name), 1) || RIGHT(LOWER(first_name), 1) || LEFT(LOWER(last_name), 1) || RIGHT(LOWER(last_name), 1) || LENGTH(first_name) || LENGTH(last_name) || REPLACE(UPPER(name), ' ', '')
FROM t1;

CAST

e.g. If you have three seperate columns for day, month and year. We can change the month name to a number and concatenate these three columns with hyphens and tell the database that the result is a date.

SELECT *,
  DATE_PART('month', TO_DATE(month, 'month')) AS clean_month /*Convert month names to numbers */
  year || '-' || DATE_PART('month', TO_DATE(month, 'month')) || '-' || day AS concatenated_date 
  CAST (year || '-' || DATE_PART('month', TO_DATE(month, 'month')) || '-' || day AS date) AS formatted_date
FROM ad_clicks

DATE_PART(‘month’, TO_DATE(month, ‘month’)) here changed a month name into the number associated with that particular month. Then you can change a string to a date using CAST. CAST is actually useful to change lots of column types. Commonly you might be doing as you saw here, where you change a string to a date using CAST(date_column AS DATE). However, you might want to make other changes to your columns in terms of their data types. In this example, instead of CAST(date_column AS DATE), you can use date_column::DATE.

Most of the functions presented in this lesson are specific to strings. They won’t work with dates, integers or floating-point numbers. However, using any of these functions will automatically change the data to the appropriate type. LEFT, RIGHT, and TRIM are all used to select only certain elements of strings, but using them to select elements of a number or date will treat them as strings for the purpose of the function. Though we didn’t cover TRIM in this lesson explicitly, it can be used to remove characters from the beginning and end of a string. This can remove unwanted spaces at the beginning or end of a row that often happen with data being moved from Excel or other storage systems.

Questions

  1. In the given dataset the format of the date column is mm/dd/yyyy with times that are not correct also at the end of the date. Remember dates should be in the format yyyy-mm-dd. Write a query to change the date to the correct SQL date format. Then use either CAST or :: to convert to a date.
SELECT date orig_date, (SUBSTR(date, 7, 4) || '-' || LEFT(date, 2) || '-' || SUBSTR(date, 4, 2))::DATE new_date
FROM sf_crime_data;

COALESCE

Occassionally you’ll end up with a datset that contains some nulls that you’d prefer to have some actual values. For e.g. we may want to label a NULL value in the primary_poc column as “no poc” so the results will be easily understanable. You can use COALESCE to replace the NULL values.

SELECT *,
  COALESCE(primary_poc, 'no POC') AS primary_poc_modified
FROM accounts
WHERE primary_poc is NULL;

In general, COALESCE returns the first non-NULL value passed for each row. You can use COALESCE to fill NULL values so that you gey values in every cell. This will have an effect on functions such as COUNT

Questions

  1. Use COALSECE to fill in the accounts.id column with the account.id for the NULL value
SELECT COALESCE(a.id, a.id) filled_id, a.name, a.website, a.lat, a.long, a.primary_poc, a.sales_rep_id, o.*
FROM accounts a
LEFT JOIN orders o
ON a.id = o.account_id
WHERE o.total IS NULL;
  1. Use COALSECE to fill in the orders.account_id column with the account.id for the NULL value
SELECT COALESCE(a.id, a.id) filled_id, a.name, a.website, a.lat, a.long, a.primary_poc, a.sales_rep_id, COALESCE(o.account_id, a.id) account_id, o.occurred_at, o.standard_qty, o.gloss_qty, o.poster_qty, o.total, o.standard_amt_usd, o.gloss_amt_usd, o.poster_amt_usd, o.total_amt_usd
FROM accounts a
LEFT JOIN orders o
ON a.id = o.account_id
WHERE o.total IS NULL;
  1. USE COALESCE to fill in ecah of the qty and usd columns with 0.
SELECT COALESCE(a.id, a.id) filled_id, a.name, a.website, a.lat, a.long, a.primary_poc, a.sales_rep_id, COALESCE(o.account_id, a.id) account_id, o.occurred_at, COALESCE(o.standard_qty, 0) standard_qty, COALESCE(o.gloss_qty,0) gloss_qty, COALESCE(o.poster_qty,0) poster_qty, COALESCE(o.total,0) total, COALESCE(o.standard_amt_usd,0) standard_amt_usd, COALESCE(o.gloss_amt_usd,0) gloss_amt_usd, COALESCE(o.poster_amt_usd,0) poster_amt_usd, COALESCE(o.total_amt_usd,0) total_amt_usd
FROM accounts a
LEFT JOIN orders o
ON a.id = o.account_id
WHERE o.total IS NULL;

LS0tCnRpdGxlOiAiTm90ZXMgb24gU1FMIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiMjIEVudGl0eSByZWxhdGlvbnNoaXAgZGlhZ3JhbXMKVGhlICJjcm93J3MgZm9vdCIgdGhhdCBjb25uZWN0cyB0aGUgdGFibGVzIHRvZ2V0aGVyIHNob3dzIHVzIGhvdyB0aGUgY29sdW1ucyBpbiBvbmUgdGFibGUgcmVsYXRlIHRvIHRoZSBjb2x1bW5zIGluIGFub3RoZXIgdGFibGUuIAohW10oaW1hZ2VzL0VSRC5wbmcpCiMjIFR5cGVzIE9mIFN0YXRlbWVudHMKIEEgZmV3IHN0YXRlbWVudHMgaW5jbHVkZToKKiBDUkVBVEUgVEFCTEUgaXMgYSBzdGF0ZW1lbnQgdGhhdCBjcmVhdGVzIGEgbmV3IHRhYmxlIGluIGEgZGF0YWJhc2UuCiogRFJPUCBUQUJMRSBpcyBhIHN0YXRlbWVudCB0aGF0IHJlbW92ZXMgYSB0YWJsZSBpbiBhIGRhdGFiYXNlLgoqIFNFTEVDVCBhbGxvd3MgeW91IHRvIHJlYWQgZGF0YSBhbmQgZGlzcGxheSBpdC4gVGhpcyBpcyBjYWxsZWQgYSBxdWVyeS4KCiMjIFNFTEVDVCAmIEZST00gU3RhdGVtZW50cwpIZXJlIHlvdSB3ZXJlIGludHJvZHVjZWQgdG8gdHdvIHN0YXRlbWVudHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gZXZlcnkgcXVlcnkgeW91IHdyaXRlCiogU0VMRUNUIC0gd2hhdCBjb2x1bW5zIHlvdSB3YW50IGJhY2suCiogRlJPTSAtIHdoYXQgdGFibGUgeW91IGFyZSBxdWVyeWluZyBmcm9tLiAKYGBge3NxbH0KU0VMRUNUIGlkLCBhY2NvdW50X2lkLCBvY2N1cnJlZF9hdApGUk9NIG9yZGVyczsKYGBgCgojIyBMSU1JVCBTdGF0ZW1lbnQKIFRoZSBMSU1JVCBzdGF0ZW1lbnQgaXMgdXNlZnVsIHdoZW4geW91IHdhbnQgdG8gc2VlIGp1c3QgdGhlIGZpcnN0IGZldyByb3dzIG9mIGEgdGFibGUuIFRoaXMgY2FuIGJlIG11Y2ggZmFzdGVyIGZvciBsb2FkaW5nIHRoYW4gaWYgd2UgbG9hZCB0aGUgZW50aXJlIGRhdGFzZXQuClRoZSBMSU1JVCBjb21tYW5kIGlzIGFsd2F5cyB0aGUgdmVyeSBsYXN0IHBhcnQgb2YgYSBxdWVyeS4gQW4gZXhhbXBsZSBvZiBzaG93aW5nIGp1c3QgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIG9yZGVycyB0YWJsZSB3aXRoIGFsbCBvZiB0aGUgY29sdW1ucyBtaWdodCBsb29rIGxpa2UgdGhlIGZvbGxvd2luZzoKYGBge3NxbH0KU0VMRUNUICoKRlJPTSBvcmRlcnMKTElNSVQgMTAKYGBgCgojIyBPUkRFUiBCWSBTdGF0ZW1lbnQKKiBUaGUgT1JERVIgQlkgc3RhdGVtZW50IGFsbG93cyB1cyB0byBvcmRlciBvdXIgdGFibGUgYnkgYW55IHJvdy4gCiogUmVtZW1iZXIgREVTQyBjYW4gYmUgYWRkZWQgYWZ0ZXIgdGhlIGNvbHVtbiBpbiB5b3VyIE9SREVSIEJZIHN0YXRlbWVudCB0byBzb3J0IGluIGRlc2NlbmRpbmcgb3JkZXIsIGFzIHRoZSBkZWZhdWx0IGlzIHRvIHNvcnQgaW4gYXNjZW5kaW5nIG9yZGVyLgpgYGB7c3FsfQpTRUxFQ1QgaWQsIG9jY3VycmVkX2F0LCB0b3RhbF9hbXRfdXNkCkZST00gb3JkZXJzCk9SREVSIEJZIG9jY3VycmVkX2F0CkxJTUlUIDEwOwpgYGAKCldlIGNhbiBPUkRFUiBCWSBtb3JlIHRoYW4gb25lIGNvbHVtbiBhdCBhIHRpbWUuIFRoZSBzdGF0ZW1lbnQgc29ydHMgYWNjb3JkaW5nIHRvIGNvbHVtbnMgbGlzdGVkIGZyb20gbGVmdCBmaXJzdCBhbmQgdGhvc2UgbGlzdGVkIG9uIHRoZSByaWdodCBhZnRlciB0aGF0LgpgYGB7c3FsfQpTRUxFQ1QgKgpGUk9NIG9yZGVycwpPUkRFUiBCWSBvY2N1cnJlZF9hdCwgdG90YWxfYW10X3VzZApMSU1JVCAxMDsKYGBgCgojIyBXSEVSRSBTdGF0ZW1lbnRzClVzaW5nIHRoZSAqKldIRVJFKiogc3RhdGVtZW50LCB3ZSBjYW4gc3Vic2V0IG91dCB0YWJsZXMgYmFzZWQgb24gY29uZGl0aW9ucyB0aGF0IG11c3QgYmUgbWV0LgpgYGB7c3FsfQpTRUxFQ1QgKgpGUk9NIG9yZGVycwpXSEVSRSBnbG9zc19hbXRfdXNkID49IDEwMDAKTElNSVQgNTsKYGBgCgpUaGUgV0hFUkUgc3RhdGVtZW50IGNhbiBhbHNvIGJlIHVzZWQgd2l0aCBub24tbnVtZXJpY2FsIGRhdGEuIFdlIGNhbiB1c2UgdGhlID0gYW5kICE9IG9wZXJhdG9ycyBoZXJlLiBZb3UgYWxzbyBuZWVkIHRvIGJlIHN1cmUgdG8gdXNlIHNpbmdsZSBxdW90ZXMKYGBge3NxbH0KU0VMRUNUIG5hbWUsIHdlYnNpdGUsIHByaW1hcnlfcG9jCkZST00gYWNjb3VudHMKV0hFUkUgbmFtZSA9ICdFeHhvbiBNb2JpbCc7CmBgYAoKIyMgQXJpdGhtZXRpYyBPcGVyYXRvcnMKIyMjIERlcml2ZWQgQ29sdW1ucwpDcmVhdGluZyBhIG5ldyBjb2x1bW4gdGhhdCBpcyBhIGNvbWJpbmF0aW9uIG9mIGV4aXN0aW5nIGNvbHVtbnMgaXMga25vd24gYXMgYSBkZXJpdmVkIGNvbHVtbi4KIyMjIE9yZGVyIG9mIE9wZXJhdGlvbnMKUmVtZW1iZXIgQk9NREFTIGZyb20gbWF0aHMgY2xhc3M/IFRoZSBzYW1lIG9yZGVyIG9mIG9wZXJhdGlvbnMgYXBwbHkgd2hlbiB1c2luZyBhcml0aG1ldGljIG9wZXJhdG9ycyBpbiBTUUwuCmBgYHtzcWx9ClNFTEVDVCBpZCwgYWNjb3VudF9pZCwgCiAgICAgICBwb3N0ZXJfYW10X3VzZC8oc3RhbmRhcmRfYW10X3VzZCArIGdsb3NzX2FtdF91c2QgKyBwb3N0ZXJfYW10X3VzZCkgQVMgcG9zdF9wZXIKRlJPTSBvcmRlcnM7CmBgYAojIyMgSW50cm9kdWN0aW9uIHRvIExvZ2ljYWwgT3BlcmF0b3JzCgoqKkxJS0UqKiAtIFRoaXMgYWxsb3dzIHlvdSB0byBwZXJmb3JtIG9wZXJhdGlvbnMgc2ltaWxhciB0byB1c2luZyBXSEVSRSBhbmQgPSwgYnV0IGZvciBjYXNlcyB3aGVuIHlvdSBtaWdodCBub3Qga25vdyBleGFjdGx5IHdoYXQgeW91IGFyZSBsb29raW5nIGZvci4gIFRoZSBMSUtFIG9wZXJhdG9yIGlzIGZyZXF1ZW50bHkgdXNlZCB3aXRoICUuIFRoZSAlIHRlbGxzIHVzIHRoYXQgd2UgbWlnaHQgd2FudCBhbnkgbnVtYmVyIG9mIGNoYXJhY3RlcnMgbGVhZGluZyB1cCB0byBhIHBhcnRpY3VsYXIgc2V0IG9mIGNoYXJhY3RlcnMgb3IgZm9sbG93aW5nIGEgY2VydGFpbiBzZXQgb2YgY2hhcmFjdGVycy4KYGBge3NxbH0KLyogc2VsZWN0IGFsbCB0aGUgY29tcGFuaWVzIHdob3NlIG5hbWVzIHN0YXJ0IHdpdGggJ0MnICovClNFTEVDVCBuYW1lIApGUk9NIGFjY291bnRzCldIRVJFIG5hbWUgbGlrZSAnQyUnCgovKiBzZWxlY3QgQWxsIGNvbXBhbmllcyB3aG9zZSBuYW1lcyBjb250YWluIHRoZSBzdHJpbmcgJ29uZScgc29tZXdoZXJlIGluIHRoZSBuYW1lLiAqLwpTRUxFQ1QgbmFtZQpGUk9NIGFjY291bnRzCldIRVJFIG5hbWUgTElLRSAnJW9uZSUnOwpgYGAKCioqSU4qKiAtIFRoaXMgYWxsb3dzIHlvdSB0byBwZXJmb3JtIG9wZXJhdGlvbnMgc2ltaWxhciB0byB1c2luZyBXSEVSRSBhbmQgPSwgYnV0IGZvciBtb3JlIHRoYW4gb25lIGNvbmRpdGlvbi4gIFdlIGNhbiBjaGVjayBvbmUsIHR3byBvciBtYW55IGNvbHVtbiB2YWx1ZXMgZm9yIHdoaWNoIHdlIHdhbnQgdG8gcHVsbCBkYXRhLCBidXQgYWxsIHdpdGhpbiB0aGUgc2FtZSBxdWVyeS4gClRJUCAtIEluIG1vc3QgU1FMIGVudmlyb25tZW50cywgeW91IGNhbiB1c2Ugc2luZ2xlIG9yIGRvdWJsZSBxdW90YXRpb24gbWFya3MgLSBhbmQgeW91IG1heSBORUVEIHRvIHVzZSBkb3VibGUgcXVvdGF0aW9uIG1hcmtzIGlmIHlvdSBoYXZlIGFuIGFwb3N0cm9waGUgd2l0aGluIHRoZSB0ZXh0IHlvdSBhcmUgYXR0ZW1wdGluZyB0byBwdWxsLgpgYGB7c3FsfQpTRUxFQ1QgbmFtZSwgcHJpbWFyeV9wb2MsIHNhbGVzX3JlcF9pZApGUk9NIGFjY291bnRzCldIRVJFIG5hbWUgSU4gKCdXYWxtYXJ0JywgJ1RhcmdldCcsICdOb3Jkc3Ryb20nKTsKYGBgCioqTk9UKiogLSBUaGlzIGlzIHVzZWQgd2l0aCBJTiBhbmQgTElLRSB0byBzZWxlY3QgYWxsIG9mIHRoZSByb3dzIE5PVCBMSUtFIG9yIE5PVCBJTiBhIGNlcnRhaW4gY29uZGl0aW9uLgpgYGB7c3FsfQovKiBVc2UgdGhlIGFjY291bnRzIHRhYmxlIHRvIGZpbmQgdGhlIGFjY291bnQgbmFtZSwgcHJpbWFyeSBwb2MsIGFuZCBzYWxlcyByZXAgaWQgZm9yIGFsbCBzdG9yZXMgZXhjZXB0IFdhbG1hcnQsIFRhcmdldCwgYW5kIE5vcmRzdHJvbSAqLwpTRUxFQ1QgbmFtZSwgcHJpbWFyeV9wb2MsIHNhbGVzX3JlcF9pZApGUk9NIGFjY291bnRzCldIRVJFIG5hbWUgTk9UIElOICgnV2FsbWFydCcsICdUYXJnZXQnLCAnTm9yZHN0cm9tJyk7CmBgYAoKKipBTkQqKiAmICoqQkVUV0VFTioqIC0gVGhlc2UgYWxsb3cgeW91IHRvIGNvbWJpbmUgb3BlcmF0aW9ucyB3aGVyZSBhbGwgY29tYmluZWQgY29uZGl0aW9ucyBtdXN0IGJlIHRydWUuCiogVGhlIEFORCBvcGVyYXRvciBpcyB1c2VkIHdpdGhpbiBhIFdIRVJFIHN0YXRlbWVudCB0byBjb25zaWRlciBtb3JlIHRoYW4gb25lIGxvZ2ljYWwgY2xhdXNlIGF0IGEgdGltZS4gRWFjaCB0aW1lIHlvdSBsaW5rIGEgbmV3IHN0YXRlbWVudCB3aXRoIGFuIEFORCwgeW91IHdpbGwgbmVlZCB0byBzcGVjaWZ5IHRoZSBjb2x1bW4geW91IGFyZSBpbnRlcmVzdGVkIGluIGxvb2tpbmcgYXQuCiogU29tZXRpbWVzIHdlIGNhbiBtYWtlIGEgY2xlYW5lciBzdGF0ZW1lbnQgdXNpbmcgQkVUV0VFTiB0aGFuIHdlIGNhbiB1c2luZyBBTkQuIFBhcnRpY3VsYXJseSB0aGlzIGlzIHRydWUgd2hlbiB3ZSBhcmUgdXNpbmcgdGhlIHNhbWUgY29sdW1uIGZvciBkaWZmZXJlbnQgcGFydHMgb2Ygb3VyIEFORCBzdGF0ZW1lbnQuIGUuZy46CmBgYHtzcWx9Ci8qSW5zdGVhZCBvZiB3cml0aW5nIDogV0hFUkUgY29sdW1uID49IDYgQU5EIGNvbHVtbiA8PSAxMCB3ZSBjYW4gaW5zdGVhZCB3cml0ZSovIApXSEVSRSBjb2x1bW4gQkVUV0VFTiA2IEFORCAxMApgYGAKCmBgYHtzcWx9Ci8qIFdyaXRlIGEgcXVlcnkgdGhhdCByZXR1cm5zIGFsbCB0aGUgb3JkZXJzIHdoZXJlIHRoZSBzdGFuZGFyZF9xdHkgaXMgb3ZlciAxMDAwLCB0aGUgcG9zdGVyX3F0eSBpcyAwLCBhbmQgdGhlIGdsb3NzX3F0eSBpcyAwICovClNFTEVDVCAqCkZST00gb3JkZXJzCldIRVJFIHN0YW5kYXJkX3F0eSA+IDEwMDAgQU5EIHBvc3Rlcl9xdHkgPSAwIEFORCBnbG9zc19xdHkgPSAwOwoKLyogVXNpbmcgdGhlIGFjY291bnRzIHRhYmxlIGZpbmQgYWxsIHRoZSBjb21wYW5pZXMgd2hvc2UgbmFtZXMgZG8gbm90IHN0YXJ0IHdpdGggJ0MnIGFuZCBlbmQgd2l0aCAncycgKi8KU0VMRUNUIG5hbWUKRlJPTSBhY2NvdW50cwpXSEVSRSBuYW1lIE5PVCBMSUtFICdDJScgQU5EIG5hbWUgTElLRSAnJXMnOwoKLyogVXNlIHRoZSB3ZWJfZXZlbnRzIHRhYmxlIHRvIGZpbmQgYWxsIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBpbmRpdmlkdWFscyB3aG8gd2VyZSBjb250YWN0ZWQgdmlhIG9yZ2FuaWMgb3IgYWR3b3JkcyBhbmQgc3RhcnRlZCB0aGVpciBhY2NvdW50IGF0IGFueSBwb2ludCBpbiAyMDE2IHNvcnRlZCBmcm9tIG5ld2VzdCB0byBvbGRlc3QgKi8gClNFTEVDVCAqCkZST00gd2ViX2V2ZW50cwpXSEVSRSBjaGFubmVsIElOICgnb3JnYW5pYycsICdhZHdvcmRzJykgQU5EIG9jY3VycmVkX2F0IEJFVFdFRU4gJzIwMTYtMDEtMDEnIEFORCAnMjAxNy0wMS0wMScKT1JERVIgQlkgb2NjdXJyZWRfYXQgREVTQzsKLyogIFlvdSB3aWxsIG5vdGljZSB0aGF0IHVzaW5nIEJFVFdFRU4gaXMgdHJpY2t5IGZvciBkYXRlcyEgV2hpbGUgQkVUV0VFTiBpcyBnZW5lcmFsbHkgaW5jbHVzaXZlIG9mIGVuZHBvaW50cywgaXQgYXNzdW1lcyB0aGUgdGltZSBpcyBhdCAwMDowMDowMCAoaS5lLiBtaWRuaWdodCkgZm9yIGRhdGVzLiBUaGlzIGlzIHRoZSByZWFzb24gd2h5IHdlIHNldCB0aGUgcmlnaHQtc2lkZSBlbmRwb2ludCBvZiB0aGUgcGVyaW9kIGF0ICcyMDE3LTAxLTAxJy4gKi8KYGBgCgoqKk9SKiogLSBUaGlzIGFsbG93IHlvdSB0byBjb21iaW5lIG9wZXJhdGlvbnMgd2hlcmUgYXQgbGVhc3Qgb25lIG9mIHRoZSBjb21iaW5lZCBjb25kaXRpb25zIG11c3QgYmUgdHJ1ZS4KU2ltaWxhciB0byB0aGUgQU5EIG9wZXJhdG9yLCB0aGUgT1Igb3BlcmF0b3IgY2FuIGNvbWJpbmUgbXVsdGlwbGUgc3RhdGVtZW50cy4gRWFjaCB0aW1lIHlvdSBsaW5rIGEgbmV3IHN0YXRlbWVudCB3aXRoIGFuIE9SLCB5b3Ugd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIGNvbHVtbiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gbG9va2luZyBhdC4KYGBge3NxbH0KLyogRmluZCBsaXN0IG9mIG9yZGVycyBpZHMgd2hlcmUgZWl0aGVyIGdsb3NzX3F0eSBvciBwb3N0ZXJfcXR5IGlzIGdyZWF0ZXIgdGhhbiA0MDAwLiBPbmx5IGluY2x1ZGUgdGhlIGlkIGZpZWxkIGluIHRoZSByZXN1bHRpbmcgdGFibGUuICovClNFTEVDVCBpZApGUk9NIG9yZGVycwpXSEVSRSBnbG9zc19xdHkgPiA0MDAwIE9SIHBvc3Rlcl9xdHkgPiA0MDAwOwoKLyogV3JpdGUgYSBxdWVyeSB0aGF0IHJldHVybnMgYSBsaXN0IG9mIG9yZGVycyB3aGVyZSB0aGUgc3RhbmRhcmRfcXR5IGlzIHplcm8gYW5kIGVpdGhlciB0aGUgZ2xvc3NfcXR5IG9yIHBvc3Rlcl9xdHkgaXMgb3ZlciAxMDAwLiAqLwpTRUxFQ1QgKgpGUk9NIG9yZGVycwpXSEVSRSBzdGFuZGFyZF9xdHkgPSAwIEFORCAoZ2xvc3NfcXR5ID4gMTAwMCBPUiBwb3N0ZXJfcXR5ID4gMTAwMCk7CgovKiBGaW5kIGFsbCB0aGUgY29tcGFueSBuYW1lcyB0aGF0IHN0YXJ0IHdpdGggYSAnQycgb3IgJ1cnLCBhbmQgdGhlIHByaW1hcnkgY29udGFjdCBjb250YWlucyAnYW5hJyBvciAnQW5hJywgYnV0IGl0IGRvZXNuJ3QgY29udGFpbiAnZWFuYScqLwpTRUxFQ1QgKgpGUk9NIGFjY291bnRzCldIRVJFIChuYW1lIExJS0UgJ0MlJyBPUiBuYW1lIExJS0UgJ1clJykgCiAgICAgICAgICAgQU5EICgocHJpbWFyeV9wb2MgTElLRSAnJWFuYSUnIE9SIHByaW1hcnlfcG9jIExJS0UgJyVBbmElJykgCiAgICAgICAgICAgQU5EIHByaW1hcnlfcG9jIE5PVCBMSUtFICclZWFuYSUnKTsKYGBgCgojIyBTdW1tYXJ5IG9mIGJhc2ljIGNvbWFuZHM6CiFbXShpbWFnZXMvc3VtbWFyeV9iYXNpY19jb21tYW5kcy5wbmcpCgoqKioKCiMgSk9JTlMKIyMgSW5uZXIgam9pbnMgCk9ubHkgcmV0dXJuIHJvd3MgdGhhdCBhcHBlYXIgaW4gKipib3RoIHRhYmxlcyoqCiFbXShpbWFnZXMvRVJELnBuZykgCmUuZy4gVXNpbmcgRVJEOiBQdWxsIHN0YW5kYXJkX3F0eSwgZ2xvc3NfcXR5LCBhbmQgcG9zdGVyX3F0eSBmcm9tIHRoZSBvcmRlcnMgdGFibGUsIGFuZCB0aGUgd2Vic2l0ZSBhbmQgdGhlIHByaW1hcnlfcG9jIGZyb20gdGhlIGFjY291bnRzIHRhYmxlLgpgYGB7c3FsfQpTRUxFQ1Qgb3JkZXJzLnN0YW5kYXJkX3F0eSwgb3JkZXJzLmdsb3NzX3F0eSwgCiAgICAgICBvcmRlcnMucG9zdGVyX3F0eSwgIGFjY291bnRzLndlYnNpdGUsIAogICAgICAgYWNjb3VudHMucHJpbWFyeV9wb2MKRlJPTSBvcmRlcnMKSk9JTiBhY2NvdW50cwpPTiBvcmRlcnMuYWNjb3VudF9pZCA9IGFjY291bnRzLmlkCmBgYApOb3RpY2UgdGhhdCB3ZSBuZWVkIHRvIHNwZWNpZnkgZXZlcnkgdGFibGUgYSBjb2x1bW4gY29tZXMgZnJvbSBpbiB0aGUgU0VMRUNUIHN0YXRlbWVudC4KIyMjIEFMSUFTCldoZW4gd2UgSk9JTiB0YWJsZXMgdG9nZXRoZXIsIGl0IGlzIG5pY2UgdG8gZ2l2ZSBlYWNoIHRhYmxlIGFuIGFsaWFzLiBGcmVxdWVudGx5IGFuIGFsaWFzIGlzIGp1c3QgdGhlIGZpcnN0IGxldHRlciBvZiB0aGUgdGFibGUgbmFtZS4gCmBgYHtzcWx9CkZST00gdGFibGVuYW1lIHQxIApKT0lOIHRhYmxlbmFtZTIgdDIKYGBgCgojIyMgUXVlc3Rpb25zCjEuIFByb3ZpZGUgYSB0YWJsZSBmb3IgYWxsICp3ZWJfZXZlbnRzKiBhc3NvY2lhdGVkIHdpdGggYWNjb3VudCBuYW1lIG9mICpXYWxtYXJ0Ki4gVGhlcmUgc2hvdWxkIGJlIHRocmVlIGNvbHVtbnMuIEJlIHN1cmUgdG8gaW5jbHVkZSB0aGUgcHJpbWFyeV9wb2MsIHRpbWUgb2YgdGhlIGV2ZW50LCBhbmQgdGhlIGNoYW5uZWwgZm9yIGVhY2ggZXZlbnQuIEFkZGl0aW9uYWxseSwgeW91IG1pZ2h0IGNob29zZSB0byBhZGQgYSBmb3VydGggY29sdW1uIHRvIGFzc3VyZSBvbmx5IFdhbG1hcnQgZXZlbnRzIHdlcmUgY2hvc2VuLiAKYGBge3NxbH0KU0VMRUNUIGEucHJpbWFyeV9wb2MsIHcub2NjdXJyZWRfYXQsIHcuY2hhbm5lbCwgYS5uYW1lCkZST00gd2ViX2V2ZW50cyB3CkpPSU4gYWNjb3VudHMgYQpPTiB3LmFjY291bnRfaWQgPSBhLmlkCldIRVJFIGEubmFtZSA9ICdXYWxtYXJ0JzsKYGBgCgoyLiBQcm92aWRlIGEgdGFibGUgdGhhdCBwcm92aWRlcyB0aGUgKnJlZ2lvbiogZm9yIGVhY2ggKnNhbGVzX3JlcCogYWxvbmcgd2l0aCB0aGVpciBhc3NvY2lhdGVkICphY2NvdW50cyouIFlvdXIgZmluYWwgdGFibGUgc2hvdWxkIGluY2x1ZGUgdGhyZWUgY29sdW1uczogdGhlIHJlZ2lvbiAqbmFtZSosIHRoZSBzYWxlcyByZXAgKm5hbWUqLCBhbmQgdGhlIGFjY291bnQgKm5hbWUqLiBTb3J0IHRoZSBhY2NvdW50cyBhbHBoYWJldGljYWxseSAoQS1aKSBhY2NvcmRpbmcgdG8gYWNjb3VudCBuYW1lLiAKYGBge3NxbH0KU0VMRUNUIHIubmFtZSByZWdpb24sIHMubmFtZSByZXAsIGEubmFtZSBhY2NvdW50CkZST00gc2FsZXNfcmVwcyBzCkpPSU4gcmVnaW9uIHIKT04gcy5yZWdpb25faWQgPSByLmlkCkpPSU4gYWNjb3VudHMgYQpPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKT1JERVIgQlkgYS5uYW1lOwpgYGAKCjMuIFByb3ZpZGUgdGhlICpuYW1lKiBmb3IgZWFjaCByZWdpb24gZm9yIGV2ZXJ5ICpvcmRlciosIGFzIHdlbGwgYXMgdGhlIGFjY291bnQgKm5hbWUqIGFuZCB0aGUgKnVuaXQgcHJpY2UqIHRoZXkgcGFpZCAodG90YWxfYW10X3VzZC90b3RhbCkgZm9yIHRoZSBvcmRlci4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaGF2ZSAzIGNvbHVtbnM6ICpyZWdpb24gbmFtZSwgYWNjb3VudCBuYW1lLCBhbmQgdW5pdCBwcmljZSouIEEgZmV3IGFjY291bnRzIGhhdmUgMCBmb3IgKnRvdGFsKiwgc28gIGRpdmlkZSBieSAodG90YWwgKyAwLjAxKSB0byBhc3N1cmUgbm90IGRpdmlkaW5nIGJ5IHplcm8uCmBgYHtzcWx9ClNFTEVDVCByLm5hbWUgcmVnaW9uLCBhLm5hbWUgYWNjb3VudCwgCiAgICAgICBvLnRvdGFsX2FtdF91c2QvKG8udG90YWwgKyAwLjAxKSB1bml0X3ByaWNlCkZST00gcmVnaW9uIHIKSk9JTiBzYWxlc19yZXBzIHMKT04gcy5yZWdpb25faWQgPSByLmlkCkpPSU4gYWNjb3VudHMgYQpPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKSk9JTiBvcmRlcnMgbwpPTiBvLmFjY291bnRfaWQgPSBhLmlkOwpgYGAKCiMjIE91dGVyIGpvaW5zCklmIHdlIHdhbnQgdG8gaW5jbHVkZSBkYXRhIHRoYXQgb25seSBleGlzdHMgaW4gb25lIG9mIHRoZSB0YWJsZXMgd2Ugd2FudCB0byBqb2luIHdlIGNhbiB1c2UgYW4gb3V0ZXIgam9pbgohW10oaW1hZ2VzL3R5cGVzX29mX2pvaW5zLnBuZykgCklmIHRoZXJlIGlzIG5vdCBtYXRjaGluZyBpbmZvcm1hdGlvbiBpbiB0aGUgSk9JTmVkIHRhYmxlLCB0aGVuIHlvdSB3aWxsIGhhdmUgY29sdW1ucyB3aXRoIGVtcHR5IGNlbGxzIHdpdGggYSBkYXRhIHR5cGUgY2FsbGVkIE5VTEwuCldyaXRpbmcgYSBMRUZUIGpvaW4gc3RhdGVtZW50OgohW10oaW1hZ2VzL2xlZnRfam9pbi5wbmcpIAohW10oaW1hZ2VzL2xlZnRfam9pbl9wdDIucG5nKSAKQ29tcGFyaW5nIExFRlQgYW5kIFJJR0hUIGpvaW46CiFbXShpbWFnZXMvY29tcGFyZV9sZWZ0X2FuZF9yaWdodF9qb2luLnBuZykgCkxFRlQgYW5kIFJJR0hUIGpvaW5zIGFyZSBlc3NlbnRpYWxseSBpbnRlciBjaGFuZ2VhYmxlIHNvIGl0cyBzdGFuZGFyZCB0byAqanVzdCB1c2UgIExFRlQgam9pbnMuKgoKIyMgSk9JTlMgYW5kIGZpbHRlcmluZwpJZiB5b3Ugd2FudGVkIHRvIGZpbHRlciB5b3VyIHJlc3VsdHMgc28gdGhhdCBpdCBvbmx5IHNob3dlZCBzYWxlcyBmcm9tIGEgcGFydGljdWxhciBzYWxlcyByZXAgeW91IGNvdWxkIHVzZSBXSEVSRSBidXQgaXTigJlzICoqYmV0dGVyIHRvIHVzZSBBTkQgYXMgcGFydCBvZiB0aGUgT04gc3RhdGVtZW50LioqCkEgc2ltcGxlIHJ1bGUgdG8gcmVtZW1iZXIgdGhpcyBpcyB0aGF0LCB3aGVuIHRoZSBkYXRhYmFzZSBleGVjdXRlcyB0aGlzIHF1ZXJ5LCBpdCBleGVjdXRlcyB0aGUgam9pbiBhbmQgZXZlcnl0aGluZyBpbiB0aGUgT04gY2xhdXNlIGZpcnN0LiBUaGluayBvZiB0aGlzIGFzIGJ1aWxkaW5nIHRoZSBuZXcgcmVzdWx0IHNldC4gVGhhdCByZXN1bHQgc2V0IGlzIHRoZW4gZmlsdGVyZWQgdXNpbmcgdGhlIFdIRVJFIGNsYXVzZS4KYGBge3NxbH0KU0VMRUNUIG9yZGVycy4qLCAKYWNjb3VudHMuKgpGUk9NIG9yZGVycwpMRUZUIEpPSU4gYWNjb3VudHMKT04gb3JkZXJzLmFjY291bnRfaWQgPSBhY2NvdW50cy5pZAovKiBVc2UgQU5EIGluIHN0ZWFkIG9mIFdIRVJFIGZvciBmaWx0ZXJpbmcgdGhpcyBMRUZUIEpPSU4gKi8KQU5EIGFjY291bnRzLnNhbGVzX3JlcF9pZCA9IDMyNSAKYGBgCgojIyMgUXVlc3Rpb25zCjEuIFByb3ZpZGUgYSB0YWJsZSB0aGF0IHByb3ZpZGVzIHRoZSByZWdpb24gZm9yIGVhY2ggc2FsZXNfcmVwIGFsb25nIHdpdGggdGhlaXIgYXNzb2NpYXRlZCBhY2NvdW50cy4gVGhpcyB0aW1lIG9ubHkgZm9yIHRoZSBNaWR3ZXN0IHJlZ2lvbi4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaW5jbHVkZSB0aHJlZSBjb2x1bW5zOiAqdGhlIHJlZ2lvbiBuYW1lLCB0aGUgc2FsZXMgcmVwIG5hbWUsIGFuZCB0aGUgYWNjb3VudCBuYW1lKi4gU29ydCB0aGUgYWNjb3VudHMgYWxwaGFiZXRpY2FsbHkgKEEtWikgYWNjb3JkaW5nIHRvIGFjY291bnQgbmFtZS4KYGBge3NxbH0KU0VMRUNUIHIubmFtZSByZWdpb24sIHMubmFtZSByZXAsIGEubmFtZSBhY2NvdW50CkZST00gc2FsZXNfcmVwcyBzCkpPSU4gcmVnaW9uIHIKT04gcy5yZWdpb25faWQgPSByLmlkCkpPSU4gYWNjb3VudHMgYQpPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKV0hFUkUgci5uYW1lID0gJ01pZHdlc3QnCk9SREVSIEJZIGEubmFtZTsKYGBgCgoyLiBQcm92aWRlIGEgdGFibGUgdGhhdCBwcm92aWRlcyB0aGUgcmVnaW9uIGZvciBlYWNoIHNhbGVzX3JlcCBhbG9uZyB3aXRoIHRoZWlyIGFzc29jaWF0ZWQgYWNjb3VudHMuIFRoaXMgdGltZSBvbmx5IGZvciBhY2NvdW50cyB3aGVyZSB0aGUgc2FsZXMgcmVwIGhhcyBhIGZpcnN0IG5hbWUgc3RhcnRpbmcgd2l0aCBTIGFuZCBpbiB0aGUgTWlkd2VzdCByZWdpb24uIFlvdXIgZmluYWwgdGFibGUgc2hvdWxkIGluY2x1ZGUgdGhyZWUgY29sdW1uczogdGhlIHJlZ2lvbiBuYW1lLCB0aGUgc2FsZXMgcmVwIG5hbWUsIGFuZCB0aGUgYWNjb3VudCBuYW1lLiBTb3J0IHRoZSBhY2NvdW50cyBhbHBoYWJldGljYWxseSAoQS1aKSBhY2NvcmRpbmcgdG8gYWNjb3VudCBuYW1lLiAKYGBge3NxbH0KU0VMRUNUIHIubmFtZSByZWdpb24sIHMubmFtZSByZXAsIGEubmFtZSBhY2NvdW50CkZST00gc2FsZXNfcmVwcyBzCkpPSU4gcmVnaW9uIHIKT04gcy5yZWdpb25faWQgPSByLmlkCkpPSU4gYWNjb3VudHMgYQpPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKV0hFUkUgci5uYW1lID0gJ01pZHdlc3QnIEFORCBzLm5hbWUgTElLRSAnUyUnCk9SREVSIEJZIGEubmFtZTsKYGBgCgozLiBQcm92aWRlIGEgdGFibGUgdGhhdCBwcm92aWRlcyB0aGUgcmVnaW9uIGZvciBlYWNoIHNhbGVzX3JlcCBhbG9uZyB3aXRoIHRoZWlyIGFzc29jaWF0ZWQgYWNjb3VudHMuIFRoaXMgdGltZSBvbmx5IGZvciBhY2NvdW50cyB3aGVyZSB0aGUgc2FsZXMgcmVwIGhhcyBhIGxhc3QgbmFtZSBzdGFydGluZyB3aXRoIEsgYW5kIGluIHRoZSBNaWR3ZXN0IHJlZ2lvbi4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaW5jbHVkZSB0aHJlZSBjb2x1bW5zOiB0aGUgcmVnaW9uIG5hbWUsIHRoZSBzYWxlcyByZXAgbmFtZSwgYW5kIHRoZSBhY2NvdW50IG5hbWUuIFNvcnQgdGhlIGFjY291bnRzIGFscGhhYmV0aWNhbGx5IChBLVopIGFjY29yZGluZyB0byBhY2NvdW50IG5hbWUuCmBgYHtzcWx9ClNFTEVDVCByLm5hbWUgcmVnaW9uLCBzLm5hbWUgcmVwLCBhLm5hbWUgYWNjb3VudApGUk9NIHNhbGVzX3JlcHMgcwpKT0lOIHJlZ2lvbiByCk9OIHMucmVnaW9uX2lkID0gci5pZApKT0lOIGFjY291bnRzIGEKT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCldIRVJFIHIubmFtZSA9ICdNaWR3ZXN0JyBBTkQgcy5uYW1lIExJS0UgJyUgSyUnCk9SREVSIEJZIGEubmFtZTsKYGBgCgo0LiBQcm92aWRlIHRoZSBuYW1lIGZvciBlYWNoIHJlZ2lvbiBmb3IgZXZlcnkgb3JkZXIsIGFzIHdlbGwgYXMgdGhlIGFjY291bnQgbmFtZSBhbmQgdGhlIHVuaXQgcHJpY2UgdGhleSBwYWlkICh0b3RhbF9hbXRfdXNkL3RvdGFsKSBmb3IgdGhlIG9yZGVyLiBIb3dldmVyLCB5b3Ugc2hvdWxkIG9ubHkgcHJvdmlkZSB0aGUgcmVzdWx0cyBpZiB0aGUgc3RhbmRhcmQgb3JkZXIgcXVhbnRpdHkgZXhjZWVkcyAxMDAuIFlvdXIgZmluYWwgdGFibGUgc2hvdWxkIGhhdmUgMyBjb2x1bW5zOiByZWdpb24gbmFtZSwgYWNjb3VudCBuYW1lLCBhbmQgdW5pdCBwcmljZS4gSW4gb3JkZXIgdG8gYXZvaWQgYSBkaXZpc2lvbiBieSB6ZXJvIGVycm9yLCBhZGRpbmcgLjAxIHRvIHRoZSBkZW5vbWluYXRvciBoZXJlIGlzIGhlbHBmdWwgdG90YWxfYW10X3VzZC8odG90YWwrMC4wMSkuIApgYGB7c3FsfQpTRUxFQ1Qgci5uYW1lIHJlZ2lvbiwgYS5uYW1lIGFjY291bnQsIG8udG90YWxfYW10X3VzZC8oby50b3RhbCArIDAuMDEpIHVuaXRfcHJpY2UKRlJPTSByZWdpb24gcgpKT0lOIHNhbGVzX3JlcHMgcwpPTiBzLnJlZ2lvbl9pZCA9IHIuaWQKSk9JTiBhY2NvdW50cyBhCk9OIGEuc2FsZXNfcmVwX2lkID0gcy5pZApKT0lOIG9yZGVycyBvCk9OIG8uYWNjb3VudF9pZCA9IGEuaWQKV0hFUkUgby5zdGFuZGFyZF9xdHkgPiAxMDA7CmBgYAoKNS4gUHJvdmlkZSB0aGUgbmFtZSBmb3IgZWFjaCByZWdpb24gZm9yIGV2ZXJ5IG9yZGVyLCBhcyB3ZWxsIGFzIHRoZSBhY2NvdW50IG5hbWUgYW5kIHRoZSB1bml0IHByaWNlIHRoZXkgcGFpZCAodG90YWxfYW10X3VzZC90b3RhbCkgZm9yIHRoZSBvcmRlci4gSG93ZXZlciwgeW91IHNob3VsZCBvbmx5IHByb3ZpZGUgdGhlIHJlc3VsdHMgaWYgdGhlIHN0YW5kYXJkIG9yZGVyIHF1YW50aXR5IGV4Y2VlZHMgMTAwIGFuZCB0aGUgcG9zdGVyIG9yZGVyIHF1YW50aXR5IGV4Y2VlZHMgNTAuIFlvdXIgZmluYWwgdGFibGUgc2hvdWxkIGhhdmUgMyBjb2x1bW5zOiByZWdpb24gbmFtZSwgYWNjb3VudCBuYW1lLCBhbmQgdW5pdCBwcmljZS4gU29ydCBmb3IgdGhlIHNtYWxsZXN0IHVuaXQgcHJpY2UgZmlyc3QuIEluIG9yZGVyIHRvIGF2b2lkIGEgZGl2aXNpb24gYnkgemVybyBlcnJvciwgYWRkaW5nIC4wMSB0byB0aGUgZGVub21pbmF0b3IgaGVyZSBpcyBoZWxwZnVsICh0b3RhbF9hbXRfdXNkLyh0b3RhbCswLjAxKS4gCmBgYHtzcWx9ClNFTEVDVCByLm5hbWUgcmVnaW9uLCBhLm5hbWUgYWNjb3VudCwgby50b3RhbF9hbXRfdXNkLyhvLnRvdGFsICsgMC4wMSkgdW5pdF9wcmljZQpGUk9NIHJlZ2lvbiByCkpPSU4gc2FsZXNfcmVwcyBzCk9OIHMucmVnaW9uX2lkID0gci5pZApKT0lOIGFjY291bnRzIGEKT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCkpPSU4gb3JkZXJzIG8KT04gby5hY2NvdW50X2lkID0gYS5pZApXSEVSRSBvLnN0YW5kYXJkX3F0eSA+IDEwMCBBTkQgby5wb3N0ZXJfcXR5ID4gNTAKT1JERVIgQlkgdW5pdF9wcmljZTsKYGBgCgo2LiBQcm92aWRlIHRoZSBuYW1lIGZvciBlYWNoIHJlZ2lvbiBmb3IgZXZlcnkgb3JkZXIsIGFzIHdlbGwgYXMgdGhlIGFjY291bnQgbmFtZSBhbmQgdGhlIHVuaXQgcHJpY2UgdGhleSBwYWlkICh0b3RhbF9hbXRfdXNkL3RvdGFsKSBmb3IgdGhlIG9yZGVyLiBIb3dldmVyLCB5b3Ugc2hvdWxkIG9ubHkgcHJvdmlkZSB0aGUgcmVzdWx0cyBpZiB0aGUgc3RhbmRhcmQgb3JkZXIgcXVhbnRpdHkgZXhjZWVkcyAxMDAgYW5kIHRoZSBwb3N0ZXIgb3JkZXIgcXVhbnRpdHkgZXhjZWVkcyA1MC4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaGF2ZSAzIGNvbHVtbnM6IHJlZ2lvbiBuYW1lLCBhY2NvdW50IG5hbWUsIGFuZCB1bml0IHByaWNlLiBTb3J0IGZvciB0aGUgbGFyZ2VzdCB1bml0IHByaWNlIGZpcnN0LiBJbiBvcmRlciB0byBhdm9pZCBhIGRpdmlzaW9uIGJ5IHplcm8gZXJyb3IsIGFkZGluZyAuMDEgdG8gdGhlIGRlbm9taW5hdG9yIGhlcmUgaXMgaGVscGZ1bCAodG90YWxfYW10X3VzZC8odG90YWwrMC4wMSkuIApgYGB7c3FsfQpTRUxFQ1Qgci5uYW1lIHJlZ2lvbiwgYS5uYW1lIGFjY291bnQsIG8udG90YWxfYW10X3VzZC8oby50b3RhbCArIDAuMDEpIHVuaXRfcHJpY2UKRlJPTSByZWdpb24gcgpKT0lOIHNhbGVzX3JlcHMgcwpPTiBzLnJlZ2lvbl9pZCA9IHIuaWQKSk9JTiBhY2NvdW50cyBhCk9OIGEuc2FsZXNfcmVwX2lkID0gcy5pZApKT0lOIG9yZGVycyBvCk9OIG8uYWNjb3VudF9pZCA9IGEuaWQKV0hFUkUgby5zdGFuZGFyZF9xdHkgPiAxMDAgQU5EIG8ucG9zdGVyX3F0eSA+IDUwCk9SREVSIEJZIHVuaXRfcHJpY2UgREVTQzsKYGBgCgo3LiBXaGF0IGFyZSB0aGUgZGlmZmVyZW50IGNoYW5uZWxzIHVzZWQgYnkgYWNjb3VudCBpZCAxMDAxPyBZb3VyIGZpbmFsIHRhYmxlIHNob3VsZCBoYXZlIG9ubHkgMiBjb2x1bW5zOiBhY2NvdW50IG5hbWUgYW5kIHRoZSBkaWZmZXJlbnQgY2hhbm5lbHMuIFlvdSBjYW4gdHJ5IFNFTEVDVCBESVNUSU5DVCB0byBuYXJyb3cgZG93biB0aGUgcmVzdWx0cyB0byBvbmx5IHRoZSB1bmlxdWUgdmFsdWVzLgpgYGB7c3FsfQpTRUxFQ1QgRElTVElOQ1QgYS5uYW1lLCB3LmNoYW5uZWwKRlJPTSBhY2NvdW50cyBhClJJR0hUIEpPSU4gd2ViX2V2ZW50cyB3Ck9OIGEuaWQgPSB3LmFjY291bnRfaWQKV0hFUkUgYS5pZCA9ICcxMDAxJzsKYGBgCgo4LiBGaW5kIGFsbCB0aGUgb3JkZXJzIHRoYXQgb2NjdXJyZWQgaW4gMjAxNS4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaGF2ZSA0IGNvbHVtbnM6IG9jY3VycmVkX2F0LCBhY2NvdW50IG5hbWUsIG9yZGVyIHRvdGFsLCBhbmQgb3JkZXIgdG90YWxfYW10X3VzZC4gCmBgYHtzcWx9ClNFTEVDVCBvLm9jY3VycmVkX2F0LCBhLm5hbWUsIG8udG90YWwsIG8udG90YWxfYW10X3VzZApGUk9NIGFjY291bnRzIGEKSk9JTiBvcmRlcnMgbwpPTiBvLmFjY291bnRfaWQgPSBhLmlkCldIRVJFIG8ub2NjdXJyZWRfYXQgQkVUV0VFTiAnMDEtMDEtMjAxNScgQU5EICcwMS0wMS0yMDE2JwpPUkRFUiBCWSBvLm9jY3VycmVkX2F0IERFU0M7CmBgYAoKKioqCgojIEFHR1JFR0FUSU9OUwoKIyMgTnVsbCB2YWx1ZXMKV2hlbiBpZGVudGlmeWluZyBOVUxMcyBpbiBhIFdIRVJFIGNsYXVzZSwgd2Ugd3JpdGUgSVMgTlVMTCBvciBJUyBOT1QgTlVMTC4gV2UgZG9uJ3QgdXNlID0sIGJlY2F1c2UgTlVMTCBpc24ndCBjb25zaWRlcmVkIGEgdmFsdWUgaW4gU1FMLgoKIyMgQ09VTlQKQ291bnQgdGhlIG51bWJlciBvZiByb3dzIGluIGEgdGFibGUKYGBge3NxbH0KU0VMRUNUIENPVU5UKCopCkZST00gYWNjb3VudHM7CmBgYApDT1VOVCAqZG9lcyBub3QqIGNvbnNpZGVyIHJvd3MgdGhhdCBoYXZlICpOVUxMKiB2YWx1ZXMuIFRoZXJlZm9yZSwgdGhpcyBjYW4gYmUgdXNlZnVsIGZvciBxdWlja2x5IGlkZW50aWZ5aW5nIHdoaWNoIHJvd3MgaGF2ZSBtaXNzaW5nIGRhdGEuCgojIyBTVU0KVW5saWtlIENPVU5ULCB5b3UgY2FuIG9ubHkgdXNlIFNVTSBvbiBudW1lcmljIGNvbHVtbnMuIEhvd2V2ZXIsIFNVTSB3aWxsIGlnbm9yZSBOVUxMIHZhbHVlcyBhbmQgdHJlYXQgdGhlbSBhcyAwLgoKIyMjIFF1ZXN0aW9ucwoxLiBGaW5kIHRoZSB0b3RhbCBhbW91bnQgb2YgcG9zdGVyX3F0eSBwYXBlciBvcmRlcmVkIGluIHRoZSBvcmRlcnMgdGFibGUuCmBgYHtzcWx9ClNFTEVDVCBTVU0ocG9zdGVyX3F0eSkgQVMgdG90YWxfcG9zdGVyX3NhbGVzCkZST00gb3JkZXJzOwpgYGAKCjIuIEZpbmQgdGhlIHRvdGFsIGFtb3VudCBvZiBzdGFuZGFyZF9xdHkgcGFwZXIgb3JkZXJlZCBpbiB0aGUgb3JkZXJzIHRhYmxlLgpgYGB7c3FsfQpTRUxFQ1QgU1VNKHN0YW5kYXJkX3F0eSkgQVMgdG90YWxfc3RhbmRhcmRfc2FsZXMKRlJPTSBvcmRlcnM7CmBgYAoKMy4gRmluZCB0aGUgdG90YWwgZG9sbGFyIGFtb3VudCBvZiBzYWxlcyB1c2luZyB0aGUgdG90YWxfYW10X3VzZCBpbiB0aGUgb3JkZXJzIHRhYmxlLgpgYGB7c3FsfQpTRUxFQ1QgU1VNKHRvdGFsX2FtdF91c2QpIEFTIHRvdGFsX2RvbGxhcl9zYWxlcwpGUk9NIG9yZGVyczsKYGBgCgo0LiBGaW5kIHRoZSB0b3RhbCBhbW91bnQgc3BlbnQgb24gc3RhbmRhcmRfYW10X3VzZCBhbmQgZ2xvc3NfYW10X3VzZCBwYXBlciBmb3IgZWFjaCBvcmRlciBpbiB0aGUgb3JkZXJzIHRhYmxlLiBUaGlzIHNob3VsZCBnaXZlIGEgZG9sbGFyIGFtb3VudCBmb3IgZWFjaCBvcmRlciBpbiB0aGUgdGFibGUuCipOb3RpY2UsIHRoaXMgc29sdXRpb24gZGlkIG5vdCB1c2UgYW4gYWdncmVnYXRlLioKYGBge3NxbH0KU0VMRUNUIHN0YW5kYXJkX2FtdF91c2QgKyBnbG9zc19hbXRfdXNkIEFTIHRvdGFsX3N0YW5kYXJkX2dsb3NzCkZST00gb3JkZXJzOwpgYGAKCjUuIEZpbmQgdGhlIHN0YW5kYXJkX2FtdF91c2QgcGVyIHVuaXQgb2Ygc3RhbmRhcmRfcXR5IHBhcGVyLiBZb3VyIHNvbHV0aW9uIHNob3VsZCB1c2UgYm90aCBhbiBhZ2dyZWdhdGlvbiBhbmQgYSBtYXRoZW1hdGljYWwgb3BlcmF0b3IuIAoqTm90aWNlLCB0aGlzIHNvbHV0aW9uIHVzZWQgYm90aCBhbiBhZ2dyZWdhdGUgYW5kIG91ciBtYXRoZW1hdGljYWwgb3BlcmF0b3JzKgpgYGB7c3FsfQpTRUxFQ1QgU1VNKHN0YW5kYXJkX2FtdF91c2QpL1NVTShzdGFuZGFyZF9xdHkpIEFTIHN0YW5kYXJkX3ByaWNlX3Blcl91bml0CkZST00gb3JkZXJzOwpgYGAKCiMjIE1JTiBhbmQgTUFYCk1JTiBhbmQgTUFYIGFyZSBhZ2dyZWdhdG9ycyB0aGF0ICppZ25vcmUgTlVMTCogdmFsdWVzLgpGdW5jdGlvbmFsbHksIE1JTiBhbmQgTUFYIGFyZSBzaW1pbGFyIHRvIENPVU5UIGluIHRoYXQgdGhleSBjYW4gYmUgdXNlZCBvbiBub24tbnVtZXJpY2FsIGNvbHVtbnMuIERlcGVuZGluZyBvbiB0aGUgY29sdW1uIHR5cGUsIE1JTiB3aWxsIHJldHVybiB0aGUgbG93ZXN0IG51bWJlciwgZWFybGllc3QgZGF0ZSwgb3Igbm9uLW51bWVyaWNhbCB2YWx1ZSBhcyBlYXJseSBpbiB0aGUgYWxwaGFiZXQgYXMgcG9zc2libGUuIEFzIHlvdSBtaWdodCBzdXNwZWN0LCBNQVggZG9lcyB0aGUgb3Bwb3NpdGXigJRpdCByZXR1cm5zIHRoZSBoaWdoZXN0IG51bWJlciwgdGhlIGxhdGVzdCBkYXRlLCBvciB0aGUgbm9uLW51bWVyaWNhbCB2YWx1ZSBjbG9zZXN0IGFscGhhYmV0aWNhbGx5IHRvIOKAnFou4oCdCgojIyMgUXVlc3Rpb25zCjEuIFdoZW4gd2FzIHRoZSBlYXJsaWVzdCBvcmRlciBldmVyIHBsYWNlZD8KYGBge3NxbH0KU0VMRUNUIE1JTihvY2N1cnJlZF9hdCkgCkZST00gb3JkZXJzOwpgYGAKVHJ5IHBlcmZvcm1pbmcgdGhlIHNhbWUgcXVlcnkgYXMgaW4gcXVlc3Rpb24gMSB3aXRob3V0IHVzaW5nIGFuIGFnZ3JlZ2F0aW9uIGZ1bmN0aW9uLiAKYGBge3NxbH0KU0VMRUNUIG9jY3VycmVkX2F0IApGUk9NIG9yZGVycyAKT1JERVIgQlkgb2NjdXJyZWRfYXQKTElNSVQgMTsKYGBgCgoyLiBXaGVuIGRpZCB0aGUgbW9zdCByZWNlbnQgKGxhdGVzdCkgd2ViX2V2ZW50IG9jY3VyPwpgYGB7c3FsfQpTRUxFQ1QgTUFYKG9jY3VycmVkX2F0KQpGUk9NIHdlYl9ldmVudHM7CmBgYApUcnkgdG8gcGVyZm9ybSB0aGUgcmVzdWx0IG9mIHRoZSBwcmV2aW91cyBxdWVyeSB3aXRob3V0IHVzaW5nIGFuIGFnZ3JlZ2F0aW9uIGZ1bmN0aW9uLgpgYGB7c3FsfQpTRUxFQ1Qgb2NjdXJyZWRfYXQKRlJPTSB3ZWJfZXZlbnRzCk9SREVSIEJZIG9jY3VycmVkX2F0IERFU0MKTElNSVQgMTsKYGBgCgojIyBBVkcKQVZHIHJldHVybnMgdGhlIG1lYW4gb2YgdGhlIGRhdGEgLSB0aGF0IGlzIHRoZSBzdW0gb2YgYWxsIG9mIHRoZSB2YWx1ZXMgaW4gdGhlIGNvbHVtbiBkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2YgdmFsdWVzIGluIGEgY29sdW1uLgpUaGlzIGFnZ3JlZ2F0ZSBmdW5jdGlvbiAqaWdub3JlcyB0aGUgTlVMTCogdmFsdWVzIGluIGJvdGggdGhlIG51bWVyYXRvciBhbmQgdGhlIGRlbm9taW5hdG9yLgoKIyMjIFF1ZXN0aW9ucwoxLiBGaW5kIHRoZSBtZWFuIChBVkVSQUdFKSBhbW91bnQgc3BlbnQgcGVyIG9yZGVyIG9uIGVhY2ggcGFwZXIgdHlwZSwgYXMgd2VsbCBhcyB0aGUgbWVhbiBhbW91bnQgb2YgZWFjaCBwYXBlciB0eXBlIHB1cmNoYXNlZCBwZXIgb3JkZXIuIFlvdXIgZmluYWwgYW5zd2VyIHNob3VsZCBoYXZlIDYgdmFsdWVzIC0gb25lIGZvciBlYWNoIHBhcGVyIHR5cGUgZm9yIHRoZSBhdmVyYWdlIG51bWJlciBvZiBzYWxlcywgYXMgd2VsbCBhcyB0aGUgYXZlcmFnZSBhbW91bnQuCmBgYHtzcWx9ClNFTEVDVCBBVkcoc3RhbmRhcmRfcXR5KSBtZWFuX3N0YW5kYXJkLCBBVkcoZ2xvc3NfcXR5KSBtZWFuX2dsb3NzLCAKICAgICAgICAgICBBVkcocG9zdGVyX3F0eSkgbWVhbl9wb3N0ZXIsIEFWRyhzdGFuZGFyZF9hbXRfdXNkKSBtZWFuX3N0YW5kYXJkX3VzZCwgCiAgICAgICAgICAgQVZHKGdsb3NzX2FtdF91c2QpIG1lYW5fZ2xvc3NfdXNkLCBBVkcocG9zdGVyX2FtdF91c2QpIG1lYW5fcG9zdGVyX3VzZApGUk9NIG9yZGVyczsKYGBgCgoyLiBXaGF0IGlzIHRoZSBNRURJQU4gdG90YWxfdXNkIHNwZW50IG9uIGFsbCBvcmRlcnM/IE5vdGUsIHRoaXMgaXMgbW9yZSBhZHZhbmNlZCB0aGFuIHRoZSB0b3BpY3Mgd2UgaGF2ZSBjb3ZlcmVkIHRodXMgZmFyIHRvIGJ1aWxkIGEgZ2VuZXJhbCBzb2x1dGlvbiwgYnV0IHdlIGNhbiBoYXJkIGNvZGUgYSBzb2x1dGlvbiBpbiB0aGUgZm9sbG93aW5nIHdheS4KYGBgIHtzcWx9ClNFTEVDVCAqCkZST00gKFNFTEVDVCB0b3RhbF9hbXRfdXNkCiAgICAgIEZST00gb3JkZXJzCiAgICAgIE9SREVSIEJZIHRvdGFsX2FtdF91c2QKICAgICAgTElNSVQgMzQ1NykgQVMgVGFibGUxCk9SREVSIEJZIHRvdGFsX2FtdF91c2QgREVTQwpMSU1JVCAyOwpgYGAKU2luY2UgdGhlcmUgYXJlIDY5MTIgb3JkZXJzIC0gd2Ugd2FudCB0aGUgYXZlcmFnZSBvZiB0aGUgMzQ1NyBhbmQgMzQ1NiBvcmRlciBhbW91bnRzIHdoZW4gb3JkZXJlZC4gVGhpcyBpcyB0aGUgYXZlcmFnZSBvZiAyNDgzLjE2IGFuZCAyNDgyLjU1LiBUaGlzIGdpdmVzIHRoZSBtZWRpYW4gb2YgMjQ4Mi44NTUuIFRoaXMgb2J2aW91c2x5IGlzbid0IGFuIGlkZWFsIHdheSB0byBjb21wdXRlLiBJZiB3ZSBvYnRhaW4gbmV3IG9yZGVycywgd2Ugd291bGQgaGF2ZSB0byBjaGFuZ2UgdGhlIGxpbWl0LiBTUUwgZGlkbid0IGV2ZW4gY2FsY3VsYXRlIHRoZSBtZWRpYW4gZm9yIHVzLiBUaGUgYWJvdmUgdXNlZCBhIFNVQlFVRVJZLCBidXQgeW91IGNvdWxkIHVzZSBhbnkgbWV0aG9kIHRvIGZpbmQgdGhlIHR3byBuZWNlc3NhcnkgdmFsdWVzLCBhbmQgdGhlbiB5b3UganVzdCBuZWVkIHRoZSBhdmVyYWdlIG9mIHRoZW0uCgojIyBHUk9VUCBCWQpHUk9VUCBCWSBjYW4gYmUgdXNlZCB0byBhZ2dyZWdhdGUgZGF0YSB3aXRoaW4gc3Vic2V0cyBvZiB0aGUgZGF0YS4gRm9yIGV4YW1wbGUsIGdyb3VwaW5nIGZvciBkaWZmZXJlbnQgYWNjb3VudHMsIGRpZmZlcmVudCByZWdpb25zLCBvciBkaWZmZXJlbnQgc2FsZXMgcmVwcmVzZW50YXRpdmVzLgpBbnkgY29sdW1uIGluIHRoZSBTRUxFQ1Qgc3RhdGVtZW50IHRoYXQgaXMgbm90IHdpdGhpbiBhbiBhZ2dyZWdhdG9yIG11c3QgYmUgaW4gdGhlIEdST1VQIEJZIGNsYXVzZS4KVGhlIEdST1VQIEJZIGFsd2F5cyBnb2VzIGJldHdlZW4gV0hFUkUgYW5kIE9SREVSIEJZLgpPUkRFUiBCWSB3b3JrcyBsaWtlIFNPUlQgaW4gc3ByZWFkc2hlZXQgc29mdHdhcmUuClNRTCBldmFsdWF0ZXMgdGhlIGFnZ3JlZ2F0aW9ucyBiZWZvcmUgdGhlIExJTUlUIGNsYXVzZS4gSWYgeW91IGRvbuKAmXQgZ3JvdXAgYnkgYW55IGNvbHVtbnMsIHlvdeKAmWxsIGdldCBhIDEtcm93IHJlc3VsdOKAlG5vIHByb2JsZW0gdGhlcmUuIElmIHlvdSBncm91cCBieSBhIGNvbHVtbiB3aXRoIGVub3VnaCB1bmlxdWUgdmFsdWVzIHRoYXQgaXQgZXhjZWVkcyB0aGUgTElNSVQgbnVtYmVyLCB0aGUgYWdncmVnYXRlcyB3aWxsIGJlIGNhbGN1bGF0ZWQsIGFuZCB0aGVuIHNvbWUgcm93cyB3aWxsIHNpbXBseSBiZSBvbWl0dGVkIGZyb20gdGhlIHJlc3VsdHMuClRoaXMgaXMgYSBuaWNlIHdheSB0byBkbyB0aGluZ3MgYmVjYXVzZSB5b3Uga25vdyB5b3XigJlyZSBnb2luZyB0byBnZXQgdGhlIGNvcnJlY3QgYWdncmVnYXRlcy4gSWYgU1FMIGN1dHMgdGhlIHRhYmxlIGRvd24gdG8gMTAwIHJvd3MsIHRoZW4gcGVyZm9ybWVkIHRoZSBhZ2dyZWdhdGlvbnMsIHlvdXIgcmVzdWx0cyB3b3VsZCBiZSBzdWJzdGFudGlhbGx5IGRpZmZlcmVudC4KCk5vdGU6IFlvdSBjYW4gcmVmZXJlbmNlIHRoZSBjb2x1bW5zIGluIHlvdXIgc2VsZWN0IHN0YXRlbWVudCBpbiBHUk9VUCBCWSBhbmQgT1JERVIgQlkgY2xhdXNlcyB3aXRoIG51bWJlcnMgdGhhdCBmb2xsb3cgdGhlIG9yZGVyIHRoZXkgYXBwZWFyIGluIHRoZSBzZWxlY3Qgc3RhdGVtZW50LiBGb3IgZXhhbXBsZQpgYGB7c3FsfQpTRUxFQ1Qgc3RhbmRhcmRfcXR5LCBDT1VOVCgqKQoKRlJPTSBvcmRlcnMKCkdST1VQIEJZIDEgLyoodGhpcyAxIHJlZmVycyB0byBzdGFuZGFyZF9xdHkgc2luY2UgaXQgaXMgdGhlIGZpcnN0IG9mIHRoZSBjb2x1bW5zIGluY2x1ZGVkIGluIHRoZSBzZWxlY3Qgc3RhdGVtZW50KSovCgpPUkRFUiBCWSAxIC8qKHRoaXMgMSByZWZlcnMgdG8gc3RhbmRhcmRfcXR5IHNpbmNlIGl0IGlzIHRoZSBmaXJzdCBvZiB0aGUgY29sdW1ucyBpbmNsdWRlZCBpbiB0aGUgc2VsZWN0IHN0YXRlbWVudCkqLwpgYGAKCiMjIyBRdWVzdGlvbnMKMS4gV2hpY2ggYWNjb3VudCAoYnkgbmFtZSkgcGxhY2VkIHRoZSBlYXJsaWVzdCBvcmRlcj8gWW91ciBzb2x1dGlvbiBzaG91bGQgaGF2ZSB0aGUgYWNjb3VudCBuYW1lIGFuZCB0aGUgZGF0ZSBvZiB0aGUgb3JkZXIuCmBgYHtzcWx9ClNFTEVDVCBhLm5hbWUsIG8ub2NjdXJyZWRfYXQKRlJPTSBhY2NvdW50cyBhCkpPSU4gb3JkZXJzIG8KT04gYS5pZCA9IG8uYWNjb3VudF9pZApPUkRFUiBCWSBvY2N1cnJlZF9hdApMSU1JVCAxOwpgYGAKCjIuIEZpbmQgdGhlIHRvdGFsIHNhbGVzIGluIHVzZCBmb3IgZWFjaCBhY2NvdW50LiBZb3Ugc2hvdWxkIGluY2x1ZGUgdHdvIGNvbHVtbnMgLSB0aGUgdG90YWwgc2FsZXMgZm9yIGVhY2ggY29tcGFueSdzIG9yZGVycyBpbiB1c2QgYW5kIHRoZSBjb21wYW55IG5hbWUuCmBgYHtzcWx9ClNFTEVDVCBhLm5hbWUsIFNVTSh0b3RhbF9hbXRfdXNkKSB0b3RhbF9zYWxlcwpGUk9NIG9yZGVycyBvCkpPSU4gYWNjb3VudHMgYQpPTiBhLmlkID0gby5hY2NvdW50X2lkCkdST1VQIEJZIGEubmFtZTsKYGBgCgozLiBWaWEgd2hhdCBjaGFubmVsIGRpZCB0aGUgbW9zdCByZWNlbnQgKGxhdGVzdCkgd2ViX2V2ZW50IG9jY3VyLCB3aGljaCBhY2NvdW50IHdhcyBhc3NvY2lhdGVkIHdpdGggdGhpcyB3ZWJfZXZlbnQ/IFlvdXIgcXVlcnkgc2hvdWxkIHJldHVybiBvbmx5IHRocmVlIHZhbHVlcyAtIHRoZSBkYXRlLCBjaGFubmVsLCBhbmQgYWNjb3VudCBuYW1lLgpgYGB7c3FsfQpTRUxFQ1Qgdy5vY2N1cnJlZF9hdCwgdy5jaGFubmVsLCBhLm5hbWUKRlJPTSB3ZWJfZXZlbnRzIHcKSk9JTiBhY2NvdW50cyBhCk9OIHcuYWNjb3VudF9pZCA9IGEuaWQgCk9SREVSIEJZIHcub2NjdXJyZWRfYXQgREVTQwpMSU1JVCAxOwpgYGAKCjQuIEZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiB0aW1lcyBlYWNoIHR5cGUgb2YgY2hhbm5lbCBmcm9tIHRoZSB3ZWJfZXZlbnRzIHdhcyB1c2VkLiBZb3VyIGZpbmFsIHRhYmxlIHNob3VsZCBoYXZlIHR3byBjb2x1bW5zIC0gdGhlIGNoYW5uZWwgYW5kIHRoZSBudW1iZXIgb2YgdGltZXMgdGhlIGNoYW5uZWwgd2FzIHVzZWQuCmBgYHtzcWx9ClNFTEVDVCB3LmNoYW5uZWwsIENPVU5UKCopCkZST00gd2ViX2V2ZW50cyB3CkdST1VQIEJZIHcuY2hhbm5lbApgYGAKCjUuIFdobyB3YXMgdGhlIHByaW1hcnkgY29udGFjdCBhc3NvY2lhdGVkIHdpdGggdGhlIGVhcmxpZXN0IHdlYl9ldmVudD8KYGBge3NxbH0KU0VMRUNUIGEucHJpbWFyeV9wb2MKRlJPTSB3ZWJfZXZlbnRzIHcKSk9JTiBhY2NvdW50cyBhCk9OIGEuaWQgPSB3LmFjY291bnRfaWQKT1JERVIgQlkgdy5vY2N1cnJlZF9hdApMSU1JVCAxOwpgYGAKCjYuIFdoYXQgd2FzIHRoZSBzbWFsbGVzdCBvcmRlciBwbGFjZWQgYnkgZWFjaCBhY2NvdW50IGluIHRlcm1zIG9mIHRvdGFsIHVzZC4gUHJvdmlkZSBvbmx5IHR3byBjb2x1bW5zIC0gdGhlIGFjY291bnQgbmFtZSBhbmQgdGhlIHRvdGFsIHVzZC4gT3JkZXIgZnJvbSBzbWFsbGVzdCBkb2xsYXIgYW1vdW50cyB0byBsYXJnZXN0LgpgYGB7c3FsfSAKU0VMRUNUIGEubmFtZSwgTUlOKHRvdGFsX2FtdF91c2QpIHNtYWxsZXN0X29yZGVyCkZST00gYWNjb3VudHMgYQpKT0lOIG9yZGVycyBvCk9OIGEuaWQgPSBvLmFjY291bnRfaWQKR1JPVVAgQlkgYS5uYW1lCk9SREVSIEJZIHNtYWxsZXN0X29yZGVyOwpgYGAKU29ydCBvZiBzdHJhbmdlIHdlIGhhdmUgYSBidW5jaCBvZiBvcmRlcnMgd2l0aCBubyBkb2xsYXJzLiBXZSBtaWdodCB3YW50IHRvIGxvb2sgaW50byB0aG9zZS4gCgo3LiBGaW5kIHRoZSBudW1iZXIgb2Ygc2FsZXMgcmVwcyBpbiBlYWNoIHJlZ2lvbi4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaGF2ZSB0d28gY29sdW1ucyAtIHRoZSByZWdpb24gYW5kIHRoZSBudW1iZXIgb2Ygc2FsZXNfcmVwcy4gT3JkZXIgZnJvbSBmZXdlc3QgcmVwcyB0byBtb3N0IHJlcHMuCmBgYHtzcWx9ClNFTEVDVCByLm5hbWUsIENPVU5UKCopIG51bV9yZXBzCkZST00gcmVnaW9uIHIKSk9JTiBzYWxlc19yZXBzIHMKT04gci5pZCA9IHMucmVnaW9uX2lkCkdST1VQIEJZIHIubmFtZQpPUkRFUiBCWSBudW1fcmVwczsKYGBgCgpZb3UgY2FuIEdST1VQIEJZIG11bHRpcGxlIGNvbHVtbnMgYXQgb25jZS4gVGhpcyBpcyBvZnRlbiB1c2VmdWwgdG8gYWdncmVnYXRlIGFjcm9zcyBhIG51bWJlciBvZiBkaWZmZXJlbnQgc2VnbWVudHMuIApUaGUgb3JkZXIgb2YgY29sdW1ucyBsaXN0ZWQgaW4gdGhlICpPUkRFUiBCWSogY2xhdXNlIGRvZXMgbWFrZSBhIGRpZmZlcmVuY2UuIFlvdSBhcmUgb3JkZXJpbmcgdGhlIGNvbHVtbnMgZnJvbSBsZWZ0IHRvIHJpZ2h0LgpUaGUgb3JkZXIgb2YgY29sdW1uIG5hbWVzIGluIHlvdXIgKkdST1VQIEJZKiBjbGF1c2UgZG9lc27igJl0IG1hdHRlcuKAlHRoZSByZXN1bHRzIHdpbGwgYmUgdGhlIHNhbWUgcmVnYXJkbGVzcy4KCiMjIyBRdWVzdGlvbnMKMS4gRm9yIGVhY2ggYWNjb3VudCwgZGV0ZXJtaW5lIHRoZSBhdmVyYWdlIGFtb3VudCBvZiBlYWNoIHR5cGUgb2YgcGFwZXIgdGhleSBwdXJjaGFzZWQgYWNyb3NzIHRoZWlyIG9yZGVycy4gWW91ciByZXN1bHQgc2hvdWxkIGhhdmUgZm91ciBjb2x1bW5zIC0gb25lIGZvciB0aGUgYWNjb3VudCBuYW1lIGFuZCBvbmUgZm9yIHRoZSBhdmVyYWdlIHNwZW50IG9uIGVhY2ggb2YgdGhlIHBhcGVyIHR5cGVzLgpgYGB7c3FsfQpTRUxFQ1QgYS5uYW1lLCBBVkcoby5zdGFuZGFyZF9xdHkpIGF2Z19zdGFuZCwgQVZHKG8uZ2xvc3NfcXR5KSBhdmdfZ2xvc3MsIEFWRyhvLnBvc3Rlcl9xdHkpIGF2Z19wb3N0CkZST00gYWNjb3VudHMgYQpKT0lOIG9yZGVycyBvCk9OIGEuaWQgPSBvLmFjY291bnRfaWQKR1JPVVAgQlkgYS5uYW1lOwpgYGAKCjIuIEZvciBlYWNoIGFjY291bnQsIGRldGVybWluZSB0aGUgYXZlcmFnZSBhbW91bnQgc3BlbnQgcGVyIG9yZGVyIG9uIGVhY2ggcGFwZXIgdHlwZS4gWW91ciByZXN1bHQgc2hvdWxkIGhhdmUgZm91ciBjb2x1bW5zIC0gb25lIGZvciB0aGUgYWNjb3VudCBuYW1lIGFuZCBvbmUgZm9yIHRoZSBhdmVyYWdlIGFtb3VudCBzcGVudCBvbiBlYWNoIHBhcGVyIHR5cGUuCmBgYHtzcWx9ClNFTEVDVCBhLm5hbWUsIEFWRyhvLnN0YW5kYXJkX2FtdF91c2QpIGF2Z19zdGFuZCwgQVZHKG8uZ2xvc3NfYW10X3VzZCkgYXZnX2dsb3NzLCBBVkcoby5wb3N0ZXJfYW10X3VzZCkgYXZnX3Bvc3QKRlJPTSBhY2NvdW50cyBhCkpPSU4gb3JkZXJzIG8KT04gYS5pZCA9IG8uYWNjb3VudF9pZApHUk9VUCBCWSBhLm5hbWU7CmBgYAoKMy4gRGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgdGltZXMgYSBwYXJ0aWN1bGFyIGNoYW5uZWwgd2FzIHVzZWQgaW4gdGhlIHdlYl9ldmVudHMgdGFibGUgZm9yIGVhY2ggc2FsZXMgcmVwLiBZb3VyIGZpbmFsIHRhYmxlIHNob3VsZCBoYXZlIHRocmVlIGNvbHVtbnMgLSB0aGUgbmFtZSBvZiB0aGUgc2FsZXMgcmVwLCB0aGUgY2hhbm5lbCwgYW5kIHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMuIE9yZGVyIHlvdXIgdGFibGUgd2l0aCB0aGUgaGlnaGVzdCBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgZmlyc3QuCmBgYHtzcWx9ClNFTEVDVCBzLm5hbWUsIHcuY2hhbm5lbCwgQ09VTlQoKikgbnVtX2V2ZW50cwpGUk9NIGFjY291bnRzIGEKSk9JTiB3ZWJfZXZlbnRzIHcKT04gYS5pZCA9IHcuYWNjb3VudF9pZApKT0lOIHNhbGVzX3JlcHMgcwpPTiBzLmlkID0gYS5zYWxlc19yZXBfaWQKR1JPVVAgQlkgcy5uYW1lLCB3LmNoYW5uZWwKT1JERVIgQlkgbnVtX2V2ZW50cyBERVNDOwpgYGAKCjQuIERldGVybWluZSB0aGUgbnVtYmVyIG9mIHRpbWVzIGEgcGFydGljdWxhciBjaGFubmVsIHdhcyB1c2VkIGluIHRoZSB3ZWJfZXZlbnRzIHRhYmxlIGZvciBlYWNoIHJlZ2lvbi4gWW91ciBmaW5hbCB0YWJsZSBzaG91bGQgaGF2ZSB0aHJlZSBjb2x1bW5zIC0gdGhlIHJlZ2lvbiBuYW1lLCB0aGUgY2hhbm5lbCwgYW5kIHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMuIE9yZGVyIHlvdXIgdGFibGUgd2l0aCB0aGUgaGlnaGVzdCBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgZmlyc3QuCmBgYHtzcWx9ClNFTEVDVCByLm5hbWUsIHcuY2hhbm5lbCwgQ09VTlQoKikgbnVtX2V2ZW50cwpGUk9NIGFjY291bnRzIGEKSk9JTiB3ZWJfZXZlbnRzIHcKT04gYS5pZCA9IHcuYWNjb3VudF9pZApKT0lOIHNhbGVzX3JlcHMgcwpPTiBzLmlkID0gYS5zYWxlc19yZXBfaWQKSk9JTiByZWdpb24gcgpPTiByLmlkID0gcy5yZWdpb25faWQKR1JPVVAgQlkgci5uYW1lLCB3LmNoYW5uZWwKT1JERVIgQlkgbnVtX2V2ZW50cyBERVNDOwpgYGAKCiMjIERJU1RJTkNUIApESVNUSU5DVCBpcyBhbHdheXMgdXNlZCBpbiBTRUxFQ1Qgc3RhdGVtZW50cywgYW5kIGl0IHByb3ZpZGVzIHRoZSB1bmlxdWUgcm93cyBmb3IgYWxsIGNvbHVtbnMgd3JpdHRlbiBpbiB0aGUgU0VMRUNUIHN0YXRlbWVudC4gVGhlcmVmb3JlLCB5b3Ugb25seSB1c2UgRElTVElOQ1Qgb25jZSBpbiBhbnkgcGFydGljdWxhciBTRUxFQ1Qgc3RhdGVtZW50LgpgYGB7c3FsfQpTRUxFQ1QgRElTVElOQ1QgY29sdW1uMSwgY29sdW1uMiwgY29sdW1uMwpGUk9NIHRhYmxlMTsKYGBgCndoaWNoIHdvdWxkIHJldHVybiB0aGUgdW5pcXVlIChvciBESVNUSU5DVCkgcm93cyBhY3Jvc3MgYWxsIHRocmVlIGNvbHVtbnMuCk5vdGU6IHVzaW5nIERJU1RJTkNULCBwYXJ0aWN1bGFybHkgaW4gYWdncmVnYXRpb25zLCBjYW4gc2xvdyB5b3VyIHF1ZXJpZXMgZG93biBxdWl0ZSBhIGJpdC4KCiMjIyBRdWVzdGlvbnMKMS4gVXNlIERJU1RJTkNUIHRvIHRlc3QgaWYgdGhlcmUgYXJlIGFueSBhY2NvdW50cyBhc3NvY2lhdGVkIHdpdGggbW9yZSB0aGFuIG9uZSByZWdpb24uCgpUaGUgYmVsb3cgdHdvIHF1ZXJpZXMgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2YgcmVzdWx0aW5nIHJvd3MgKDM1MSksIHNvIHdlIGtub3cgdGhhdCBldmVyeSBhY2NvdW50IGlzIGFzc29jaWF0ZWQgd2l0aCBvbmx5IG9uZSByZWdpb24uIElmIGVhY2ggYWNjb3VudCB3YXMgYXNzb2NpYXRlZCB3aXRoIG1vcmUgdGhhbiBvbmUgcmVnaW9uLCB0aGUgZmlyc3QgcXVlcnkgc2hvdWxkIGhhdmUgcmV0dXJuZWQgbW9yZSByb3dzIHRoYW4gdGhlIHNlY29uZCBxdWVyeS4KYGBge3NxbH0KU0VMRUNUIGEuaWQgYXMgImFjY291bnQgaWQiLCByLmlkIGFzICJyZWdpb24gaWQiLCAKYS5uYW1lIGFzICJhY2NvdW50IG5hbWUiLCByLm5hbWUgYXMgInJlZ2lvbiBuYW1lIgpGUk9NIGFjY291bnRzIGEKSk9JTiBzYWxlc19yZXBzIHMKT04gcy5pZCA9IGEuc2FsZXNfcmVwX2lkCkpPSU4gcmVnaW9uIHIKT04gci5pZCA9IHMucmVnaW9uX2lkOwpgYGAKYW5kCmBgYHtzcWx9ClNFTEVDVCBESVNUSU5DVCBpZCwgbmFtZQpGUk9NIGFjY291bnRzOwpgYGAKMi4gSGF2ZSBhbnkgc2FsZXMgcmVwcyB3b3JrZWQgb24gbW9yZSB0aGFuIG9uZSBhY2NvdW50PwoKQWN0dWFsbHkgYWxsIG9mIHRoZSBzYWxlcyByZXBzIGhhdmUgd29ya2VkIG9uIG1vcmUgdGhhbiBvbmUgYWNjb3VudC4gVGhlIGZld2VzdCBudW1iZXIgb2YgYWNjb3VudHMgYW55IHNhbGVzIHJlcCB3b3JrcyBvbiBpcyAzLiBUaGVyZSBhcmUgNTAgc2FsZXMgcmVwcywgYW5kIHRoZXkgYWxsIGhhdmUgbW9yZSB0aGFuIG9uZSBhY2NvdW50LiBVc2luZyBESVNUSU5DVCBpbiB0aGUgc2Vjb25kIHF1ZXJ5IGFzc3VyZXMgdGhhdCBhbGwgb2YgdGhlIHNhbGVzIHJlcHMgYXJlIGFjY291bnRlZCBmb3IgaW4gdGhlIGZpcnN0IHF1ZXJ5LgpgYGB7c3FsfQpTRUxFQ1Qgcy5pZCwgcy5uYW1lLCBDT1VOVCgqKSBudW1fYWNjb3VudHMKRlJPTSBhY2NvdW50cyBhCkpPSU4gc2FsZXNfcmVwcyBzCk9OIHMuaWQgPSBhLnNhbGVzX3JlcF9pZApHUk9VUCBCWSBzLmlkLCBzLm5hbWUKT1JERVIgQlkgbnVtX2FjY291bnRzOwpgYGAKCmBgYHtzcWx9ClNFTEVDVCBESVNUSU5DVCBpZCwgbmFtZQpGUk9NIHNhbGVzX3JlcHM7CmBgYAoKIyMgSEFWSU5HCkhBVklORyBpcyB0aGUg4oCcY2xlYW7igJ0gd2F5IHRvIGZpbHRlciBhIHF1ZXJ5IHRoYXQgaGFzIGJlZW4gYWdncmVnYXRlZCwgYnV0IHRoaXMgaXMgYWxzbyBjb21tb25seSBkb25lIHVzaW5nIGEgc3VicXVlcnkuIEVzc2VudGlhbGx5LCBhbnkgdGltZSB5b3Ugd2FudCB0byBwZXJmb3JtIGEgV0hFUkUgb24gYW4gZWxlbWVudCBvZiB5b3VyIHF1ZXJ5IHRoYXQgd2FzIGNyZWF0ZWQgYnkgYW4gYWdncmVnYXRlLCB5b3UgbmVlZCB0byB1c2UgSEFWSU5HIGluc3RlYWQuCkhBVklORyBhcHBlYXJzIGFmdGVyIHRoZSBHUk9VUCBCWSBjbGF1c2UgYnV0IGJlZm9yZSB0aGUgT1JERVIgQlkgY2xhdXNlLgoKIyMjIFF1ZXN0aW9ucwoxLiBIb3cgbWFueSBvZiB0aGUgc2FsZXMgcmVwcyBoYXZlIG1vcmUgdGhhbiA1IGFjY291bnRzIHRoYXQgdGhleSBtYW5hZ2U/CmBgYHtzcWx9ClNFTEVDVCBzLmlkLCBzLm5hbWUsIENPVU5UKCopIG51bV9hY2NvdW50cwpGUk9NIGFjY291bnRzIGEKSk9JTiBzYWxlc19yZXBzIHMKT04gcy5pZCA9IGEuc2FsZXNfcmVwX2lkCkdST1VQIEJZIHMuaWQsIHMubmFtZQpIQVZJTkcgQ09VTlQoKikgPiA1Ck9SREVSIEJZIG51bV9hY2NvdW50czsKYGBgCgoyLiBIb3cgbWFueSBhY2NvdW50cyBoYXZlIG1vcmUgdGhhbiAyMCBvcmRlcnM/CmBgYHtzcWx9ClNFTEVDVCBhLmlkLCBhLm5hbWUsIENPVU5UKCopIG51bV9vcmRlcnMKRlJPTSBhY2NvdW50cyBhCkpPSU4gb3JkZXJzIG8KT04gYS5pZCA9IG8uYWNjb3VudF9pZApHUk9VUCBCWSBhLmlkLCBhLm5hbWUKSEFWSU5HIENPVU5UKCopID4gMjAKT1JERVIgQlkgbnVtX29yZGVyczsKYGBgCgozLiBXaGljaCBhY2NvdW50IGhhcyB0aGUgbW9zdCBvcmRlcnM/CmBgYHtzcWx9ClNFTEVDVCBhLmlkLCBhLm5hbWUsIENPVU5UKCopIG51bV9vcmRlcnMKRlJPTSBhY2NvdW50cyBhCkpPSU4gb3JkZXJzIG8KT04gYS5pZCA9IG8uYWNjb3VudF9pZApHUk9VUCBCWSBhLmlkLCBhLm5hbWUKT1JERVIgQlkgbnVtX29yZGVycyBERVNDCkxJTUlUIDE7CmBgYAoKNC4gSG93IG1hbnkgYWNjb3VudHMgc3BlbnQgbW9yZSB0aGFuIDMwLDAwMCB1c2QgdG90YWwgYWNyb3NzIGFsbCBvcmRlcnM/CmBgYHtzcWx9ClNFTEVDVCBhLmlkLCBhLm5hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX3NwZW50CkZST00gYWNjb3VudHMgYQpKT0lOIG9yZGVycyBvCk9OIGEuaWQgPSBvLmFjY291bnRfaWQKR1JPVVAgQlkgYS5pZCwgYS5uYW1lCkhBVklORyBTVU0oby50b3RhbF9hbXRfdXNkKSA+IDMwMDAwCk9SREVSIEJZIHRvdGFsX3NwZW50OwpgYGAKCjUuIEhvdyBtYW55IGFjY291bnRzIHNwZW50IGxlc3MgdGhhbiAxLDAwMCB1c2QgdG90YWwgYWNyb3NzIGFsbCBvcmRlcnM/CmBgYHtzcWx9ClNFTEVDVCBhLmlkLCBhLm5hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX3NwZW50CkZST00gYWNjb3VudHMgYQpKT0lOIG9yZGVycyBvCk9OIGEuaWQgPSBvLmFjY291bnRfaWQKR1JPVVAgQlkgYS5pZCwgYS5uYW1lCkhBVklORyBTVU0oby50b3RhbF9hbXRfdXNkKSA8IDEwMDAKT1JERVIgQlkgdG90YWxfc3BlbnQ7CmBgYAoKNi4gV2hpY2ggYWNjb3VudCBoYXMgc3BlbnQgdGhlIG1vc3Qgd2l0aCB1cz8KYGBge3NxbH0KU0VMRUNUIGEuaWQsIGEubmFtZSwgU1VNKG8udG90YWxfYW10X3VzZCkgdG90YWxfc3BlbnQKRlJPTSBhY2NvdW50cyBhCkpPSU4gb3JkZXJzIG8KT04gYS5pZCA9IG8uYWNjb3VudF9pZApHUk9VUCBCWSBhLmlkLCBhLm5hbWUKT1JERVIgQlkgdG90YWxfc3BlbnQgREVTQwpMSU1JVCAxOwpgYGAKCjcuIFdoaWNoIGFjY291bnQgaGFzIHNwZW50IHRoZSBsZWFzdCB3aXRoIHVzPwpgYGB7c3FsfQpTRUxFQ1QgYS5pZCwgYS5uYW1lLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3RhbF9zcGVudApGUk9NIGFjY291bnRzIGEKSk9JTiBvcmRlcnMgbwpPTiBhLmlkID0gby5hY2NvdW50X2lkCkdST1VQIEJZIGEuaWQsIGEubmFtZQpPUkRFUiBCWSB0b3RhbF9zcGVudApMSU1JVCAxOwpgYGAKCjguIFdoaWNoIGFjY291bnRzIHVzZWQgZmFjZWJvb2sgYXMgYSBjaGFubmVsIHRvIGNvbnRhY3QgY3VzdG9tZXJzIG1vcmUgdGhhbiA2IHRpbWVzPwpgYGB7c3FsfQpTRUxFQ1QgYS5pZCwgYS5uYW1lLCB3LmNoYW5uZWwsIENPVU5UKCopIHVzZV9vZl9jaGFubmVsCkZST00gYWNjb3VudHMgYQpKT0lOIHdlYl9ldmVudHMgdwpPTiBhLmlkID0gdy5hY2NvdW50X2lkCkdST1VQIEJZIGEuaWQsIGEubmFtZSwgdy5jaGFubmVsCkhBVklORyBDT1VOVCgqKSA+IDYgQU5EIHcuY2hhbm5lbCA9ICdmYWNlYm9vaycKT1JERVIgQlkgdXNlX29mX2NoYW5uZWw7CmBgYAoKOS4gV2hpY2ggYWNjb3VudCB1c2VkIGZhY2Vib29rIG1vc3QgYXMgYSBjaGFubmVsPyAKYGBge3NxbH0KU0VMRUNUIGEuaWQsIGEubmFtZSwgdy5jaGFubmVsLCBDT1VOVCgqKSB1c2Vfb2ZfY2hhbm5lbApGUk9NIGFjY291bnRzIGEKSk9JTiB3ZWJfZXZlbnRzIHcKT04gYS5pZCA9IHcuYWNjb3VudF9pZApXSEVSRSB3LmNoYW5uZWwgPSAnZmFjZWJvb2snCkdST1VQIEJZIGEuaWQsIGEubmFtZSwgdy5jaGFubmVsCk9SREVSIEJZIHVzZV9vZl9jaGFubmVsIERFU0MKTElNSVQgMTsKYGBgCgoxMC4gV2hpY2ggY2hhbm5lbCB3YXMgbW9zdCBmcmVxdWVudGx5IHVzZWQgYnkgbW9zdCBhY2NvdW50cz8KYGBge3NxbH0KU0VMRUNUIGEuaWQsIGEubmFtZSwgdy5jaGFubmVsLCBDT1VOVCgqKSB1c2Vfb2ZfY2hhbm5lbApGUk9NIGFjY291bnRzIGEKSk9JTiB3ZWJfZXZlbnRzIHcKT04gYS5pZCA9IHcuYWNjb3VudF9pZApHUk9VUCBCWSBhLmlkLCBhLm5hbWUsIHcuY2hhbm5lbApPUkRFUiBCWSB1c2Vfb2ZfY2hhbm5lbCBERVNDCkxJTUlUIDEwOwpgYGAKCiMjIERBVEUgRnVuY3Rpb25zCipHUk9VUCppbmcgKkJZKiBhIGRhdGUgY29sdW1uIGlzIG5vdCB1c3VhbGx5IHZlcnkgdXNlZnVsIGluIFNRTCwgYXMgdGhlc2UgY29sdW1ucyB0ZW5kIHRvIGhhdmUgdHJhbnNhY3Rpb24gZGF0YSBkb3duIHRvIGEgc2Vjb25kLiAKRGF0ZXMgYXJlIHN0b3JlZCBpbiB5ZWFyLCBtb250aCwgZGF5LCBob3VyLCBtaW51dGUsIHNlY29uZCwgd2hpY2ggaGVscHMgdXMgaW4gdHJ1bmNhdGluZy4KVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGJ1aWx0IGluIFNRTCBmdW5jdGlvbnMgdGhhdCBhcmUgYWltZWQgYXQgaGVscGluZyB1cyBpbXByb3ZlIG91ciBleHBlcmllbmNlIGluIHdvcmtpbmcgd2l0aCBkYXRlczoKSW4gU1FMIGRhdGVzIGFyZSBzdG9yZWQgaW4gdGhlIGZvcm1hdCBZWVlZLU1NLUREIHdpdGggdGhlIHRpbWUgaW4gaG91cnMsIG1pbnV0ZXMgYW5kIHNlY29uZHMgYXQgdGhlIGVuZC4gCipEQVRFX1RSVU5DKiBhbGxvd3MgeW91IHRvIHRydW5jYXRlIHlvdXIgZGF0ZSB0byBhIHBhcnRpY3VsYXIgcGFydCBvZiB5b3VyIGRhdGUtdGltZSBjb2x1bW4uIENvbW1vbiB0cnVuY3Rpb25zIGFyZSBkYXksIG1vbnRoLCBhbmQgeWVhci4KKkRBVEVfUEFSVCogY2FuIGJlIHVzZWZ1bCBmb3IgcHVsbGluZyBhIHNwZWNpZmljIHBvcnRpb24gb2YgYSBkYXRlLCBidXQgbm90aWNlIHB1bGxpbmcgbW9udGggb3IgZGF5IG9mIHRoZSB3ZWVrIChkb3cpIG1lYW5zIHRoYXQgeW91IGFyZSBubyBsb25nZXIga2VlcGluZyB0aGUgeWVhcnMgaW4gb3JkZXIuIFJhdGhlciB5b3UgYXJlIGdyb3VwaW5nIGZvciBjZXJ0YWluIGNvbXBvbmVudHMgcmVnYXJkbGVzcyBvZiB3aGljaCB5ZWFyIHRoZXkgYmVsb25nZWQgaW4uCipOb3RlOiogRE9XIChkYXkgb2Ygd2VlaykgcmV0dXJucyBhIHZhbHVlIGZyb20gMC02IHdoZXJlIDAgaXMgU3VuZGF5IGFuZCA2IGlzIFNhdHVyZGF5LiAKCiMjIyBRdWVzdGlvbnMKMS4gRmluZCB0aGUgc2FsZXMgaW4gdGVybXMgb2YgdG90YWwgZG9sbGFycyBmb3IgYWxsIG9yZGVycyBpbiBlYWNoICp5ZWFyKiwgb3JkZXJlZCBmcm9tIGdyZWF0ZXN0IHRvIGxlYXN0LiBEbyB5b3Ugbm90aWNlIGFueSB0cmVuZHMgaW4gdGhlIHllYXJseSBzYWxlcyB0b3RhbHM/CmBgYHtzcWx9ClNFTEVDVCBEQVRFX1BBUlQoJ3llYXInLCBvY2N1cnJlZF9hdCkgb3JkX3llYXIsICBTVU0odG90YWxfYW10X3VzZCkgdG90YWxfc3BlbnQKRlJPTSBvcmRlcnMKR1JPVVAgQlkgMQpPUkRFUiBCWSAyIERFU0M7CmBgYApXaGVuIHdlIGxvb2sgYXQgdGhlIHllYXJseSB0b3RhbHMsIHlvdSBtaWdodCBub3RpY2UgdGhhdCAyMDEzIGFuZCAyMDE3IGhhdmUgbXVjaCBzbWFsbGVyIHRvdGFscyB0aGFuIGFsbCBvdGhlciB5ZWFycy4gSWYgd2UgbG9vayBmdXJ0aGVyIGF0IHRoZSBtb250aGx5IGRhdGEsIHdlIHNlZSB0aGF0IGZvciAyMDEzIGFuZCAyMDE3IHRoZXJlIGlzIG9ubHkgb25lIG1vbnRoIG9mIHNhbGVzIGZvciBlYWNoIG9mIHRoZXNlIHllYXJzICgxMiBmb3IgMjAxMyBhbmQgMSBmb3IgMjAxNykuIFRoZXJlZm9yZSwgbmVpdGhlciBvZiB0aGVzZSBhcmUgZXZlbmx5IHJlcHJlc2VudGVkLiBTYWxlcyBoYXZlIGJlZW4gaW5jcmVhc2luZyB5ZWFyIG92ZXIgeWVhciwgd2l0aCAyMDE2IGJlaW5nIHRoZSBsYXJnZXN0IHNhbGVzIHRvIGRhdGUuIEF0IHRoaXMgcmF0ZSwgd2UgbWlnaHQgZXhwZWN0IDIwMTcgdG8gaGF2ZSB0aGUgbGFyZ2VzdCBzYWxlcy4KCjIuIFdoaWNoICptb250aCogZGlkIFBhcmNoICYgUG9zZXkgaGF2ZSB0aGUgZ3JlYXRlc3Qgc2FsZXMgaW4gdGVybXMgb2YgdG90YWwgZG9sbGFycz8gQXJlIGFsbCBtb250aHMgZXZlbmx5IHJlcHJlc2VudGVkIGJ5IHRoZSBkYXRhc2V0PwoKSW4gb3JkZXIgZm9yIHRoaXMgdG8gYmUgJ2ZhaXInLCB3ZSBzaG91bGQgcmVtb3ZlIHRoZSBzYWxlcyBmcm9tIDIwMTMgYW5kIDIwMTcuIEZvciB0aGUgc2FtZSByZWFzb25zIGFzIGRpc2N1c3NlZCBhYm92ZS4KYGBge3NxbH0KU0VMRUNUIERBVEVfUEFSVCgnbW9udGgnLCBvY2N1cnJlZF9hdCkgb3JkX21vbnRoLCBTVU0odG90YWxfYW10X3VzZCkgdG90YWxfc3BlbnQKRlJPTSBvcmRlcnMKV0hFUkUgb2NjdXJyZWRfYXQgQkVUV0VFTiAnMjAxNC0wMS0wMScgQU5EICcyMDE3LTAxLTAxJwpHUk9VUCBCWSAxCk9SREVSIEJZIDIgREVTQzsKYGBgClRoZSBncmVhdGVzdCBzYWxlcyBhbW91bnRzIG9jY3VyIGluIERlY2VtYmVyICgxMikuCgozLiBXaGljaCAqeWVhciogZGlkIFBhcmNoICYgUG9zZXkgaGF2ZSB0aGUgZ3JlYXRlc3Qgc2FsZXMgaW4gdGVybXMgb2YgdG90YWwgbnVtYmVyIG9mIG9yZGVycz8gQXJlIGFsbCB5ZWFycyBldmVubHkgcmVwcmVzZW50ZWQgYnkgdGhlIGRhdGFzZXQ/CmBgYHtzcWx9ClNFTEVDVCBEQVRFX1BBUlQoJ3llYXInLCBvY2N1cnJlZF9hdCkgb3JkX3llYXIsICBDT1VOVCgqKSB0b3RhbF9zYWxlcwpGUk9NIG9yZGVycwpHUk9VUCBCWSAxCk9SREVSIEJZIDIgREVTQzsKYGBgCkFnYWluLCAyMDE2IGJ5IGZhciBoYXMgdGhlIG1vc3QgYW1vdW50IG9mIG9yZGVycywgYnV0IGFnYWluIDIwMTMgYW5kIDIwMTcgYXJlIG5vdCBldmVubHkgcmVwcmVzZW50ZWQgdG8gdGhlIG90aGVyIHllYXJzIGluIHRoZSBkYXRhc2V0LgoKNC4gV2hpY2ggKm1vbnRoKiBkaWQgUGFyY2ggJiBQb3NleSBoYXZlIHRoZSBncmVhdGVzdCBzYWxlcyBpbiB0ZXJtcyBvZiB0b3RhbCBudW1iZXIgb2Ygb3JkZXJzPyBBcmUgYWxsIG1vbnRocyBldmVubHkgcmVwcmVzZW50ZWQgYnkgdGhlIGRhdGFzZXQ/CmBgYHtzcWx9ClNFTEVDVCBEQVRFX1BBUlQoJ21vbnRoJywgb2NjdXJyZWRfYXQpIG9yZF9tb250aCwgQ09VTlQoKikgdG90YWxfc2FsZXMKRlJPTSBvcmRlcnMKV0hFUkUgb2NjdXJyZWRfYXQgQkVUV0VFTiAnMjAxNC0wMS0wMScgQU5EICcyMDE3LTAxLTAxJwpHUk9VUCBCWSAxCk9SREVSIEJZIDIgREVTQzsKYGBgCkRlY2VtYmVyIHN0aWxsIGhhcyB0aGUgbW9zdCBzYWxlcywgYnV0IGludGVyZXN0aW5nbHksIE5vdmVtYmVyIGhhcyB0aGUgc2Vjb25kIG1vc3Qgc2FsZXMgKGJ1dCBub3QgdGhlIG1vc3QgZG9sbGFyIHNhbGVzLiBUbyBtYWtlIGEgZmFpciBjb21wYXJpc29uIGZyb20gb25lIG1vbnRoIHRvIGFub3RoZXIgMjAxNyBhbmQgMjAxMyBkYXRhIHdlcmUgcmVtb3ZlZC4KCjUuIEluIHdoaWNoIG1vbnRoIG9mIHdoaWNoIHllYXIgZGlkIFdhbG1hcnQgc3BlbmQgdGhlIG1vc3Qgb24gZ2xvc3MgcGFwZXIgaW4gdGVybXMgb2YgZG9sbGFycz8KYGBge3NxbH0KU0VMRUNUIERBVEVfVFJVTkMoJ21vbnRoJywgby5vY2N1cnJlZF9hdCkgb3JkX2RhdGUsIFNVTShvLmdsb3NzX2FtdF91c2QpIHRvdF9zcGVudApGUk9NIG9yZGVycyBvIApKT0lOIGFjY291bnRzIGEKT04gYS5pZCA9IG8uYWNjb3VudF9pZApXSEVSRSBhLm5hbWUgPSAnV2FsbWFydCcKR1JPVVAgQlkgMQpPUkRFUiBCWSAyIERFU0MKTElNSVQgMTsKYGBgCk1heSAyMDE2IHdhcyB3aGVuIFdhbG1hcnQgc3BlbnQgdGhlIG1vc3Qgb24gZ2xvc3MgcGFwZXIuCgojIyBDQVNFIApUaGUgQ0FTRSBzdGF0ZW1lbnQgYWx3YXlzIGdvZXMgaW4gdGhlIFNFTEVDVCBjbGF1c2UuCkNBU0UgbXVzdCBpbmNsdWRlIHRoZSBmb2xsb3dpbmcgY29tcG9uZW50czogV0hFTiwgVEhFTiwgYW5kIEVORC4gRUxTRSBpcyBhbiBvcHRpb25hbCBjb21wb25lbnQgdG8gY2F0Y2ggY2FzZXMgdGhhdCBkaWRu4oCZdCBtZWV0IGFueSBvZiB0aGUgb3RoZXIgcHJldmlvdXMgQ0FTRSBjb25kaXRpb25zLgpZb3UgY2FuIG1ha2UgYW55IGNvbmRpdGlvbmFsIHN0YXRlbWVudCB1c2luZyBhbnkgY29uZGl0aW9uYWwgb3BlcmF0b3IgKGxpa2UgV0hFUkUpIGJldHdlZW4gV0hFTiBhbmQgVEhFTi4gVGhpcyBpbmNsdWRlcyBzdHJpbmdpbmcgdG9nZXRoZXIgbXVsdGlwbGUgY29uZGl0aW9uYWwgc3RhdGVtZW50cyB1c2luZyBBTkQgYW5kIE9SLgpZb3UgY2FuIGluY2x1ZGUgbXVsdGlwbGUgV0hFTiBzdGF0ZW1lbnRzLCBhcyB3ZWxsIGFzIGFuIEVMU0Ugc3RhdGVtZW50IGFnYWluLCB0byBkZWFsIHdpdGggYW55IHVuYWRkcmVzc2VkIGNvbmRpdGlvbnMuCiMjIyBFeGFtcGxlCkluIGEgcXVpeiBxdWVzdGlvbiBpbiB0aGUgcHJldmlvdXMgQmFzaWMgU1FMIGxlc3NvbiwgeW91IHNhdyB0aGlzIHF1ZXN0aW9uOgoxLiBDcmVhdGUgYSBjb2x1bW4gdGhhdCBkaXZpZGVzIHRoZSBzdGFuZGFyZF9hbXRfdXNkIGJ5IHRoZSBzdGFuZGFyZF9xdHkgdG8gZmluZCB0aGUgdW5pdCBwcmljZSBmb3Igc3RhbmRhcmQgcGFwZXIgZm9yIGVhY2ggb3JkZXIuIExpbWl0IHRoZSByZXN1bHRzIHRvIHRoZSBmaXJzdCAxMCBvcmRlcnMsIGFuZCBpbmNsdWRlIHRoZSBpZCBhbmQgYWNjb3VudF9pZCBmaWVsZHMuICpOT1RFIC0geW91IHdpbGwgYmUgdGhyb3duIGFuIGVycm9yIHdpdGggdGhlIGNvcnJlY3Qgc29sdXRpb24gdG8gdGhpcyBxdWVzdGlvbi4gVGhpcyBpcyBmb3IgYSBkaXZpc2lvbiBieSB6ZXJvLiBZb3Ugd2lsbCBsZWFybiBob3cgdG8gZ2V0IGEgc29sdXRpb24gd2l0aG91dCBhbiBlcnJvciB0byB0aGlzIHF1ZXJ5IHdoZW4geW91IGxlYXJuIGFib3V0IENBU0Ugc3RhdGVtZW50cyBpbiBhIGxhdGVyIHNlY3Rpb24uKgpMZXQncyBzZWUgaG93IHdlIGNhbiB1c2UgdGhlIENBU0Ugc3RhdGVtZW50IHRvIGdldCBhcm91bmQgdGhpcyBlcnJvci4KYGBge3NxbH0KU0VMRUNUIGlkLCBhY2NvdW50X2lkLCBzdGFuZGFyZF9hbXRfdXNkL3N0YW5kYXJkX3F0eSBBUyB1bml0X3ByaWNlCkZST00gb3JkZXJzCkxJTUlUIDEwOwpgYGAKTm93LCBsZXQncyB1c2UgYSBDQVNFIHN0YXRlbWVudC4gVGhpcyB3YXkgYW55IHRpbWUgdGhlIHN0YW5kYXJkX3F0eSBpcyB6ZXJvLCB3ZSB3aWxsIHJldHVybiAwLCBhbmQgb3RoZXJ3aXNlIHdlIHdpbGwgcmV0dXJuIHRoZSB1bml0X3ByaWNlLgpgYGB7c3FsfQpTRUxFQ1QgYWNjb3VudF9pZCwgQ0FTRSBXSEVOIHN0YW5kYXJkX3F0eSA9IDAgT1Igc3RhbmRhcmRfcXR5IElTIE5VTEwgVEhFTiAwCiAgICAgICAgICAgICAgICAgICAgICAgIEVMU0Ugc3RhbmRhcmRfYW10X3VzZC9zdGFuZGFyZF9xdHkgRU5EIEFTIHVuaXRfcHJpY2UKRlJPTSBvcmRlcnMKTElNSVQgMTA7CmBgYApOb3cgdGhlIGZpcnN0IHBhcnQgb2YgdGhlIHN0YXRlbWVudCB3aWxsIGNhdGNoIGFueSBvZiB0aG9zZSBkaXZpc2lvbiBieSB6ZXJvIHZhbHVlcyB0aGF0IHdlcmUgY2F1c2luZyB0aGUgZXJyb3IsIGFuZCB0aGUgb3RoZXIgY29tcG9uZW50cyB3aWxsIGNvbXB1dGUgdGhlIGRpdmlzaW9uIGFzIG5lY2Vzc2FyeS4gWW91IHdpbGwgbm90aWNlLCB3ZSBlc3NlbnRpYWxseSBjaGFyZ2UgYWxsIG9mIG91ciBhY2NvdW50cyA0Ljk5IGZvciBzdGFuZGFyZCBwYXBlci4gSXQgbWFrZXMgc2Vuc2UgdGhpcyBkb2Vzbid0IGZsdWN0dWF0ZSwgYW5kIGl0IGlzIG1vcmUgYWNjdXJhdGUgdGhhbiBhZGRpbmcgMSBpbiB0aGUgZGVub21pbmF0b3IgbGlrZSBvdXIgcXVpY2sgZml4IG1pZ2h0IGhhdmUgYmVlbiBpbiB0aGUgZWFybGllciBsZXNzb24uCgojIyMgUXVlc3Rpb25zCjEuIFdlIHdvdWxkIGxpa2UgdG8gdW5kZXJzdGFuZCAzIGRpZmZlcmVudCBsZXZlbHMgb2YgY3VzdG9tZXJzIGJhc2VkIG9uIHRoZSBhbW91bnQgYXNzb2NpYXRlZCB3aXRoIHRoZWlyIHB1cmNoYXNlcy4gVGhlIHRvcCBicmFuY2ggaW5jbHVkZXMgYW55b25lIHdpdGggYSBMaWZldGltZSBWYWx1ZSAodG90YWwgc2FsZXMgb2YgYWxsIG9yZGVycykgZ3JlYXRlciB0aGFuIDIwMCwwMDAgdXNkLiBUaGUgc2Vjb25kIGJyYW5jaCBpcyBiZXR3ZWVuIDIwMCwwMDAgYW5kIDEwMCwwMDAgdXNkLiBUaGUgbG93ZXN0IGJyYW5jaCBpcyBhbnlvbmUgdW5kZXIgMTAwLDAwMCB1c2QuIFByb3ZpZGUgYSB0YWJsZSB0aGF0IGluY2x1ZGVzIHRoZSBsZXZlbCBhc3NvY2lhdGVkIHdpdGggZWFjaCBhY2NvdW50LiBZb3Ugc2hvdWxkIHByb3ZpZGUgdGhlIGFjY291bnQgbmFtZSwgdGhlIHRvdGFsIHNhbGVzIG9mIGFsbCBvcmRlcnMgZm9yIHRoZSBjdXN0b21lciwgYW5kIHRoZSBsZXZlbC4gT3JkZXIgd2l0aCB0aGUgdG9wIHNwZW5kaW5nIGN1c3RvbWVycyBsaXN0ZWQgZmlyc3QuCmBgYHtzcWx9ClNFTEVDVCBhLm5hbWUsIFNVTSh0b3RhbF9hbXRfdXNkKSB0b3RhbF9zcGVudCwgCiAgICAgQ0FTRSBXSEVOIFNVTSh0b3RhbF9hbXRfdXNkKSA+IDIwMDAwMCBUSEVOICd0b3AnCiAgICAgV0hFTiAgU1VNKHRvdGFsX2FtdF91c2QpID4gMTAwMDAwIFRIRU4gJ21pZGRsZScKICAgICBFTFNFICdsb3cnIEVORCBBUyBjdXN0b21lcl9sZXZlbApGUk9NIG9yZGVycyBvCkpPSU4gYWNjb3VudHMgYQpPTiBvLmFjY291bnRfaWQgPSBhLmlkIApHUk9VUCBCWSBhLm5hbWUKT1JERVIgQlkgMiBERVNDOwpgYGAKCjIuIFdlIHdvdWxkIG5vdyBsaWtlIHRvIHBlcmZvcm0gYSBzaW1pbGFyIGNhbGN1bGF0aW9uIHRvIHRoZSBmaXJzdCwgYnV0IHdlIHdhbnQgdG8gb2J0YWluIHRoZSB0b3RhbCBhbW91bnQgc3BlbnQgYnkgY3VzdG9tZXJzIG9ubHkgaW4gMjAxNiBhbmQgMjAxNy4gS2VlcCB0aGUgc2FtZSBsZXZlbHMgYXMgaW4gdGhlIHByZXZpb3VzIHF1ZXN0aW9uLiBPcmRlciB3aXRoIHRoZSB0b3Agc3BlbmRpbmcgY3VzdG9tZXJzIGxpc3RlZCBmaXJzdC4KYGBge3NxbH0KU0VMRUNUIGEubmFtZSwgU1VNKHRvdGFsX2FtdF91c2QpIHRvdGFsX3NwZW50LCAKICAgICBDQVNFIFdIRU4gU1VNKHRvdGFsX2FtdF91c2QpID4gMjAwMDAwIFRIRU4gJ3RvcCcKICAgICBXSEVOICBTVU0odG90YWxfYW10X3VzZCkgPiAxMDAwMDAgVEhFTiAnbWlkZGxlJwogICAgIEVMU0UgJ2xvdycgRU5EIEFTIGN1c3RvbWVyX2xldmVsCkZST00gb3JkZXJzIG8KSk9JTiBhY2NvdW50cyBhCk9OIG8uYWNjb3VudF9pZCA9IGEuaWQKV0hFUkUgb2NjdXJyZWRfYXQgPiAnMjAxNS0xMi0zMScgCkdST1VQIEJZIDEKT1JERVIgQlkgMiBERVNDOwpgYGAKCjMuIFdlIHdvdWxkIGxpa2UgdG8gaWRlbnRpZnkgdG9wIHBlcmZvcm1pbmcgc2FsZXMgcmVwcywgd2hpY2ggYXJlIHNhbGVzIHJlcHMgYXNzb2NpYXRlZCB3aXRoIG1vcmUgdGhhbiAyMDAgb3JkZXJzLiBDcmVhdGUgYSB0YWJsZSB3aXRoIHRoZSBzYWxlcyByZXAgbmFtZSwgdGhlIHRvdGFsIG51bWJlciBvZiBvcmRlcnMsIGFuZCBhIGNvbHVtbiB3aXRoIHRvcCBvciBub3QgZGVwZW5kaW5nIG9uIGlmIHRoZXkgaGF2ZSBtb3JlIHRoYW4gMjAwIG9yZGVycy4gUGxhY2UgdGhlIHRvcCBzYWxlcyBwZW9wbGUgZmlyc3QgaW4geW91ciBmaW5hbCB0YWJsZS4KYGBge3NxbH0KU0VMRUNUIHMubmFtZSwgQ09VTlQoKikgbnVtX29yZHMsCiAgICAgQ0FTRSBXSEVOIENPVU5UKCopID4gMjAwIFRIRU4gJ3RvcCcKICAgICBFTFNFICdub3QnIEVORCBBUyBzYWxlc19yZXBfbGV2ZWwKRlJPTSBvcmRlcnMgbwpKT0lOIGFjY291bnRzIGEKT04gby5hY2NvdW50X2lkID0gYS5pZCAKSk9JTiBzYWxlc19yZXBzIHMKT04gcy5pZCA9IGEuc2FsZXNfcmVwX2lkCkdST1VQIEJZIHMubmFtZQpPUkRFUiBCWSAyIERFU0M7CmBgYApJdCBpcyB3b3J0aCBtZW50aW9uaW5nIHRoYXQgdGhpcyBhc3N1bWVzIGVhY2ggbmFtZSBpcyB1bmlxdWUgLSB3aGljaCBoYXMgYmVlbiBkb25lIGEgZmV3IHRpbWVzLiBXZSBvdGhlcndpc2Ugd291bGQgd2FudCB0byBicmVhayBieSB0aGUgbmFtZSBhbmQgdGhlIGlkIG9mIHRoZSB0YWJsZS4KCjQuIFRoZSBwcmV2aW91cyBkaWRuJ3QgYWNjb3VudCBmb3IgdGhlIG1pZGRsZSwgbm9yIHRoZSBkb2xsYXIgYW1vdW50IGFzc29jaWF0ZWQgd2l0aCB0aGUgc2FsZXMuIE1hbmFnZW1lbnQgZGVjaWRlcyB0aGV5IHdhbnQgdG8gc2VlIHRoZXNlIGNoYXJhY3RlcmlzdGljcyByZXByZXNlbnRlZCBhcyB3ZWxsLiBXZSB3b3VsZCBsaWtlIHRvIGlkZW50aWZ5IHRvcCBwZXJmb3JtaW5nIHNhbGVzIHJlcHMsIHdoaWNoIGFyZSBzYWxlcyByZXBzIGFzc29jaWF0ZWQgd2l0aCBtb3JlIHRoYW4gMjAwIG9yZGVycyBvciBtb3JlIHRoYW4gNzUwMDAwIGluIHRvdGFsIHNhbGVzLiBUaGUgbWlkZGxlIGdyb3VwIGhhcyBhbnkgcmVwIHdpdGggbW9yZSB0aGFuIDE1MCBvcmRlcnMgb3IgNTAwMDAwIGluIHNhbGVzLiBDcmVhdGUgYSB0YWJsZSB3aXRoIHRoZSBzYWxlcyByZXAgbmFtZSwgdGhlIHRvdGFsIG51bWJlciBvZiBvcmRlcnMsIHRvdGFsIHNhbGVzIGFjcm9zcyBhbGwgb3JkZXJzLCBhbmQgYSBjb2x1bW4gd2l0aCB0b3AsIG1pZGRsZSwgb3IgbG93IGRlcGVuZGluZyBvbiB0aGlzIGNyaXRlcmlhLiBQbGFjZSB0aGUgdG9wIHNhbGVzIHBlb3BsZSBiYXNlZCBvbiBkb2xsYXIgYW1vdW50IG9mIHNhbGVzIGZpcnN0IGluIHlvdXIgZmluYWwgdGFibGUuIFlvdSBtaWdodCBzZWUgYSBmZXcgdXBzZXQgc2FsZXMgcGVvcGxlIGJ5IHRoaXMgY3JpdGVyaWEhCmBgYHtzcWx9ClNFTEVDVCBzLm5hbWUsIENPVU5UKCopLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3RhbF9zcGVudCwgCiAgICAgQ0FTRSBXSEVOIENPVU5UKCopID4gMjAwIE9SIFNVTShvLnRvdGFsX2FtdF91c2QpID4gNzUwMDAwIFRIRU4gJ3RvcCcKICAgICBXSEVOIENPVU5UKCopID4gMTUwIE9SIFNVTShvLnRvdGFsX2FtdF91c2QpID4gNTAwMDAwIFRIRU4gJ21pZGRsZScKICAgICBFTFNFICdsb3cnIEVORCBBUyBzYWxlc19yZXBfbGV2ZWwKRlJPTSBvcmRlcnMgbwpKT0lOIGFjY291bnRzIGEKT04gby5hY2NvdW50X2lkID0gYS5pZCAKSk9JTiBzYWxlc19yZXBzIHMKT04gcy5pZCA9IGEuc2FsZXNfcmVwX2lkCkdST1VQIEJZIHMubmFtZQpPUkRFUiBCWSAzIERFU0M7CmBgYAoKKioqCgojIFN1YnF1ZXJpZXMKV2hlbmV2ZXIgd2UgbmVlZCB0byB1c2UgZXhpc3RpbmcgdGFibGVzIHRvIGNyZWF0ZSBhIG5ldyB0YWJsZSB0aGF0IHdlIHRoZW4gd2FudCB0byBxdWVyeSBhZ2FpbiwgdGhpcyBpcyBhbiBpbmRpY2F0aW9uIHRoYXQgd2Ugd2lsbCBuZWVkIHRvIHVzZSBzb21lIHNvcnQgb2YgKnN1YnF1ZXJ5Ki4KCmUuZy5GaW5kIHRoZSBhdmVyYWdlIG51bWJlciBvZiBldmVudHMgdGhhdCBvY2N1ciBmb3IgZWFjaCBkYXkgZm9yIGVhY2ggY2hhbm5lbDoKKGkpIEZpcnN0LCB3ZSBuZWVkZWQgdG8gZ3JvdXAgYnkgdGhlIGRheSBhbmQgY2hhbm5lbC4gVGhlbiBvcmRlcmluZyBieSB0aGUgbnVtYmVyIG9mIGV2ZW50cyAodGhlIHRoaXJkIGNvbHVtbikgZ2F2ZSB1cyBhIHF1aWNrIHdheSB0byBhbnN3ZXIgdGhlIGZpcnN0IHF1ZXN0aW9uLgpgYGB7c3FsfQpTRUxFQ1QgREFURV9UUlVOQygnZGF5JyxvY2N1cnJlZF9hdCkgQVMgZGF5LAogICBjaGFubmVsLCBDT1VOVCgqKSBhcyBldmVudHMKRlJPTSB3ZWJfZXZlbnRzCkdST1VQIEJZIDEsMgpPUkRFUiBCWSAzIERFU0M7CmBgYAooaWkpSGVyZSB5b3UgY2FuIHNlZSB0aGF0IHRvIGdldCB0aGUgZW50aXJlIHRhYmxlIGluIHF1ZXN0aW9uIDEgYmFjaywgd2UgaW5jbHVkZWQgYW4gKiBpbiBvdXIgU0VMRUNUIHN0YXRlbWVudC4gWW91IHdpbGwgbmVlZCB0byBiZSBzdXJlIHRvIGFsaWFzIHlvdXIgdGFibGUuCmBgYHtzcWx9ClNFTEVDVCAqCkZST00gKFNFTEVDVCBEQVRFX1RSVU5DKCdkYXknLG9jY3VycmVkX2F0KSBBUyBkYXksCiAgICAgICAgICAgY2hhbm5lbCwgQ09VTlQoKikgYXMgZXZlbnRzCiAgICAgRlJPTSB3ZWJfZXZlbnRzIAogICAgIEdST1VQIEJZIDEsMgogICAgIE9SREVSIEJZIDMgREVTQykgc3ViOwpgYGAKKGlpaSkgRmluYWxseSwgaGVyZSB3ZSBhcmUgYWJsZSB0byBnZXQgYSB0YWJsZSB0aGF0IHNob3dzIHRoZSBhdmVyYWdlIG51bWJlciBvZiBldmVudHMgYSBkYXkgZm9yIGVhY2ggY2hhbm5lbC4uIFNpbmNlIHlvdSBicm9rZSBvdXQgYnkgZGF5IGluIGlubmVyIHF1ZXJ5LCB0aGlzIGlzIGdpdmluZyB5b3UgYW4gYXZlcmFnZSBwZXIgZGF5LgpgYGB7c3FsfQpTRUxFQ1QgY2hhbm5lbCwgQVZHKGV2ZW50cykgQVMgYXZlcmFnZV9ldmVudHMKRlJPTSAoU0VMRUNUIERBVEVfVFJVTkMoJ2RheScsb2NjdXJyZWRfYXQpIEFTIGRheSwKICAgICAgICAgICAgIGNoYW5uZWwsIENPVU5UKCopIGFzIGV2ZW50cwogICAgICBGUk9NIHdlYl9ldmVudHMgCiAgICAgIEdST1VQIEJZIDEsMikgc3ViCkdST1VQIEJZIGNoYW5uZWwKT1JERVIgQlkgMiBERVNDOwpgYGAKKkZvcm1hdCBzdWJxdWVyaWVzIHVzaW5nIGluZGVudHMgaW4gb3JkZXIgdG8gbWFrZSBpdCBlYXNpZXIgdG8gcmVhZCoKCiMjIFN1YnF1ZXJpZXMgUGFydCBJSQpJbiB0aGUgZmlyc3Qgc3VicXVlcnkgeW91IHdyb3RlLCB5b3UgY3JlYXRlZCBhIHRhYmxlIHRoYXQgeW91IGNvdWxkIHRoZW4gcXVlcnkgYWdhaW4gaW4gdGhlIEZST00gc3RhdGVtZW50LiBIb3dldmVyLCBpZiB5b3UgYXJlIG9ubHkgcmV0dXJuaW5nIGEgc2luZ2xlIHZhbHVlLCB5b3UgbWlnaHQgdXNlIHRoYXQgdmFsdWUgaW4gYSBsb2dpY2FsIHN0YXRlbWVudCBsaWtlIFdIRVJFLCBIQVZJTkcsIG9yIGV2ZW4gU0VMRUNUIC0gdGhlIHZhbHVlIGNvdWxkIGJlIG5lc3RlZCB3aXRoaW4gYSBDQVNFIHN0YXRlbWVudC4gSXQgY2FuIGJlIHVzZWQgYW55ZWh3cmUgeW91IG1pZ2h0IGhhdmUgYSB0YWJsZSBuYW1lLCBvciBhIGNvbHVtbiBuYW1lIG9yIGluZGl2aWR1YWwgdmFsdWUuCipOb3RlKiB0aGF0IHlvdSBzaG91bGQgbm90IGluY2x1ZGUgYW4gYWxpYXMgd2hlbiB5b3Ugd3JpdGUgYSBzdWJxdWVyeSBpbiBhIGNvbmRpdGlvbmFsIHN0YXRlbWVudC4gVGhpcyBpcyBiZWNhdXNlIHRoZSBzdWJxdWVyeSBpcyB0cmVhdGVkIGFzIGFuIGluZGl2aWR1YWwgdmFsdWUgKG9yIHNldCBvZiB2YWx1ZXMgaW4gdGhlIElOIGNhc2UpIHJhdGhlciB0aGFuIGFzIGEgdGFibGUuCgplLmcuIFJldHVybiB0aGUgYXZlcmFnZSBxdWFudGl0eSBvZiBlYWNoIHR5cGUgb2YgcGFwZXIgdGhhdCB3YXMgc29sZCBpbiB0aGUgc2FtZSBtb3RoIGFzIFBhcmNoICYgUG9zZXlzIGZpcnN0IG9yZGVyIGV2ZXIuIAooaSlUbyBnZXQgdGhlIGRhdGUgb2YgdGhlIGZpcnN0IG9yZGVyIHlvdSBjb3VsZCB3cml0ZSBhIHN1YnF1ZXJ5IHdpdGggYSBtaW4gZnVuY3Rpb24uIApgYGB7c3FsfQpTRUxFQ1QgREFURV9UUlVOQygnbW9udGgnLCBNSU4ob2NjdXJyZWRfYXQpKSAKRlJPTSBvcmRlcnM7CmBgYAooaWkpVGhlbiB0byBwdWxsIHRoZSBhdmVyYWdlIGZvciBlYWNoLCB3ZSBjb3VsZCBkbyB0aGlzIGFsbCBpbiBvbmUgcXVlcnksIGJ1dCBmb3IgcmVhZGFiaWxpdHksIEkgcHJvdmlkZWQgdHdvIHF1ZXJpZXMgYmVsb3cgdG8gcGVyZm9ybSBlYWNoIHNlcGFyYXRlbHkuCmBgYHtzcWx9ClNFTEVDVCBBVkcoc3RhbmRhcmRfcXR5KSBhdmdfc3RkLCBBVkcoZ2xvc3NfcXR5KSBhdmdfZ2xzLCBBVkcocG9zdGVyX3F0eSkgYXZnX3BzdApGUk9NIG9yZGVycwpXSEVSRSBEQVRFX1RSVU5DKCdtb250aCcsIG9jY3VycmVkX2F0KSA9IAogICAgIChTRUxFQ1QgREFURV9UUlVOQygnbW9udGgnLCBNSU4ob2NjdXJyZWRfYXQpKSBGUk9NIG9yZGVycyk7CgpTRUxFQ1QgU1VNKHRvdGFsX2FtdF91c2QpCkZST00gb3JkZXJzCldIRVJFIERBVEVfVFJVTkMoJ21vbnRoJywgb2NjdXJyZWRfYXQpID0gCiAgICAgIChTRUxFQ1QgREFURV9UUlVOQygnbW9udGgnLCBNSU4ob2NjdXJyZWRfYXQpKSBGUk9NIG9yZGVycyk7CmBgYApBbHNvLCBub3RpY2UgdGhlIHF1ZXJ5IGhlcmUgY29tcGFyZWQgYSBzaW5nbGUgdmFsdWUuIE1vc3QgY29uZGl0aW9uYWwgbG9naWMgd2lsbCB3b3JrIHdpdGggc3VicXVlcmllcyBjb250YWluaW5nIG9uZSBjZWxsIHJlc3VsdHMuIElOIGlzIHRoZSBvbmx5IHR5cGUgb2YgY29uZGl0aW9uYWwgbG9naWMgdGhhdCB3aWxsIHdvcmsgd2hlbiB0aGUgaW5uZXIgcXVlcnkgY29udGFpbnMgbXVsdGlwbGUgcmVzdWx0cy4gSWYgd2UgcmV0dXJuZWQgYW4gZW50aXJlIGNvbHVtbiBJTiB3b3VsZCBuZWVkIHRvIGJlIHVzZWQgdG8gcGVyZm9ybSBhIGxvZ2ljYWwgYXJndW1lbnQuIElmIHdlIGFyZSByZXR1cm5pbmcgYW4gZW50aXJlIHRhYmxlLCB0aGVuIHdlIG11c3QgdXNlIGFuIEFMSUFTIGZvciB0aGUgdGFibGUsIGFuZCBwZXJmb3JtIGFkZGl0aW9uYWwgbG9naWMgb24gdGhlIGVudGlyZSB0YWJsZS4KCiMjIyBRdWVzdGlvbnMKMS4gUHJvdmlkZSB0aGUgbmFtZSBvZiB0aGUgc2FsZXNfcmVwIGluIGVhY2ggcmVnaW9uIHdpdGggdGhlIGxhcmdlc3QgYW1vdW50IG9mIHRvdGFsX2FtdF91c2Qgc2FsZXMuCkZpcnN0LCBJIHdhbnRlZCB0byBmaW5kIHRoZSB0b3RhbF9hbXRfdXNkIHRvdGFscyBhc3NvY2lhdGVkIHdpdGggZWFjaCBzYWxlcyByZXAsIGFuZCBJIGFsc28gd2FudGVkIHRoZSByZWdpb24gaW4gd2hpY2ggdGhleSB3ZXJlIGxvY2F0ZWQuIFRoZSBxdWVyeSBiZWxvdyBwcm92aWRlZCB0aGlzIGluZm9ybWF0aW9uLgpgYGB7c3FsfQpTRUxFQ1Qgcy5uYW1lIHJlcF9uYW1lLCByLm5hbWUgcmVnaW9uX25hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX2FtdApGUk9NIHNhbGVzX3JlcHMgcwpKT0lOIGFjY291bnRzIGEKT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCkpPSU4gb3JkZXJzIG8KT04gby5hY2NvdW50X2lkID0gYS5pZApKT0lOIHJlZ2lvbiByCk9OIHIuaWQgPSBzLnJlZ2lvbl9pZApHUk9VUCBCWSAxLDIKT1JERVIgQlkgMyBERVNDOwpgYGAKTmV4dCwgSSBwdWxsZWQgdGhlIG1heCBmb3IgZWFjaCByZWdpb24sIGFuZCB0aGVuIHdlIGNhbiB1c2UgdGhpcyB0byBwdWxsIHRob3NlIHJvd3MgaW4gb3VyIGZpbmFsIHJlc3VsdC4KYGBge3NxbH0KU0VMRUNUIHJlZ2lvbl9uYW1lLCBNQVgodG90YWxfYW10KSB0b3RhbF9hbXQKICAgICBGUk9NKFNFTEVDVCBzLm5hbWUgcmVwX25hbWUsIHIubmFtZSByZWdpb25fbmFtZSwgU1VNKG8udG90YWxfYW10X3VzZCkgdG90YWxfYW10CiAgICAgICAgICAgICBGUk9NIHNhbGVzX3JlcHMgcwogICAgICAgICAgICAgSk9JTiBhY2NvdW50cyBhCiAgICAgICAgICAgICBPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKICAgICAgICAgICAgIEpPSU4gb3JkZXJzIG8KICAgICAgICAgICAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgICAgICAgICAgIEpPSU4gcmVnaW9uIHIKICAgICAgICAgICAgIE9OIHIuaWQgPSBzLnJlZ2lvbl9pZAogICAgICAgICAgICAgR1JPVVAgQlkgMSwgMikgdDEKICAgICBHUk9VUCBCWSAxOwpgYGAKRXNzZW50aWFsbHksIHRoaXMgaXMgYSBKT0lOIG9mIHRoZXNlIHR3byB0YWJsZXMsIHdoZXJlIHRoZSByZWdpb24gYW5kIGFtb3VudCBtYXRjaC4KYGBge3NxbH0KU0VMRUNUIHQzLnJlcF9uYW1lLCB0My5yZWdpb25fbmFtZSwgdDMudG90YWxfYW10CkZST00oU0VMRUNUIHJlZ2lvbl9uYW1lLCBNQVgodG90YWxfYW10KSB0b3RhbF9hbXQKICAgICBGUk9NKFNFTEVDVCBzLm5hbWUgcmVwX25hbWUsIHIubmFtZSByZWdpb25fbmFtZSwgU1VNKG8udG90YWxfYW10X3VzZCkgdG90YWxfYW10CiAgICAgICAgICAgICBGUk9NIHNhbGVzX3JlcHMgcwogICAgICAgICAgICAgSk9JTiBhY2NvdW50cyBhCiAgICAgICAgICAgICBPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKICAgICAgICAgICAgIEpPSU4gb3JkZXJzIG8KICAgICAgICAgICAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgICAgICAgICAgIEpPSU4gcmVnaW9uIHIKICAgICAgICAgICAgIE9OIHIuaWQgPSBzLnJlZ2lvbl9pZAogICAgICAgICAgICAgR1JPVVAgQlkgMSwgMikgdDEKICAgICBHUk9VUCBCWSAxKSB0MgpKT0lOIChTRUxFQ1Qgcy5uYW1lIHJlcF9uYW1lLCByLm5hbWUgcmVnaW9uX25hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX2FtdAogICAgIEZST00gc2FsZXNfcmVwcyBzCiAgICAgSk9JTiBhY2NvdW50cyBhCiAgICAgT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCiAgICAgSk9JTiBvcmRlcnMgbwogICAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgICBKT0lOIHJlZ2lvbiByCiAgICAgT04gci5pZCA9IHMucmVnaW9uX2lkCiAgICAgR1JPVVAgQlkgMSwyCiAgICAgT1JERVIgQlkgMyBERVNDKSB0MwpPTiB0My5yZWdpb25fbmFtZSA9IHQyLnJlZ2lvbl9uYW1lIEFORCB0My50b3RhbF9hbXQgPSB0Mi50b3RhbF9hbXQ7CmBgYAoKMi5Gb3IgdGhlIHJlZ2lvbiB3aXRoIHRoZSBsYXJnZXN0IHNhbGVzIHRvdGFsX2FtdF91c2QsIGhvdyBtYW55IHRvdGFsIG9yZGVycyB3ZXJlIHBsYWNlZD8gClRoZSBmaXJzdCBxdWVyeSBJIHdyb3RlIHdhcyB0byBwdWxsIHRoZSB0b3RhbF9hbXRfdXNkIGZvciBlYWNoIHJlZ2lvbi4KYGBge3NxbH0KU0VMRUNUIHIubmFtZSByZWdpb25fbmFtZSwgU1VNKG8udG90YWxfYW10X3VzZCkgdG90YWxfYW10CkZST00gc2FsZXNfcmVwcyBzCkpPSU4gYWNjb3VudHMgYQpPTiBhLnNhbGVzX3JlcF9pZCA9IHMuaWQKSk9JTiBvcmRlcnMgbwpPTiBvLmFjY291bnRfaWQgPSBhLmlkCkpPSU4gcmVnaW9uIHIKT04gci5pZCA9IHMucmVnaW9uX2lkCkdST1VQIEJZIHIubmFtZTsKYGBgClRoZW4gd2UganVzdCB3YW50IHRoZSByZWdpb24gd2l0aCB0aGUgbWF4IGFtb3VudCBmcm9tIHRoaXMgdGFibGUuIFRoZXJlIGFyZSB0d28gd2F5cyBJIGNvbnNpZGVyZWQgZ2V0dGluZyB0aGlzIGFtb3VudC4gT25lIHdhcyB0byBwdWxsIHRoZSBtYXggdXNpbmcgYSBzdWJxdWVyeS4gQW5vdGhlciB3YXkgaXMgdG8gb3JkZXIgZGVzY2VuZGluZyBhbmQganVzdCBwdWxsIHRoZSB0b3AgdmFsdWUuCmBgYHtzcWx9ClNFTEVDVCBNQVgodG90YWxfYW10KQpGUk9NIChTRUxFQ1Qgci5uYW1lIHJlZ2lvbl9uYW1lLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3RhbF9hbXQKICAgICAgICAgICAgIEZST00gc2FsZXNfcmVwcyBzCiAgICAgICAgICAgICBKT0lOIGFjY291bnRzIGEKICAgICAgICAgICAgIE9OIGEuc2FsZXNfcmVwX2lkID0gcy5pZAogICAgICAgICAgICAgSk9JTiBvcmRlcnMgbwogICAgICAgICAgICAgT04gby5hY2NvdW50X2lkID0gYS5pZAogICAgICAgICAgICAgSk9JTiByZWdpb24gcgogICAgICAgICAgICAgT04gci5pZCA9IHMucmVnaW9uX2lkCiAgICAgICAgICAgICBHUk9VUCBCWSByLm5hbWUpIHN1YjsKYGBgCkZpbmFsbHksIHdlIHdhbnQgdG8gcHVsbCB0aGUgdG90YWwgb3JkZXJzIGZvciB0aGUgcmVnaW9uIHdpdGggdGhpcyBhbW91bnQ6CmBgYHtzcWx9ClNFTEVDVCByLm5hbWUsIFNVTShvLnRvdGFsKSB0b3RhbF9vcmRlcnMKRlJPTSBzYWxlc19yZXBzIHMKSk9JTiBhY2NvdW50cyBhCk9OIGEuc2FsZXNfcmVwX2lkID0gcy5pZApKT0lOIG9yZGVycyBvCk9OIG8uYWNjb3VudF9pZCA9IGEuaWQKSk9JTiByZWdpb24gcgpPTiByLmlkID0gcy5yZWdpb25faWQKR1JPVVAgQlkgci5uYW1lCkhBVklORyBTVU0oby50b3RhbF9hbXRfdXNkKSA9ICgKICAgICAgU0VMRUNUIE1BWCh0b3RhbF9hbXQpCiAgICAgIEZST00gKFNFTEVDVCByLm5hbWUgcmVnaW9uX25hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX2FtdAogICAgICAgICAgICAgIEZST00gc2FsZXNfcmVwcyBzCiAgICAgICAgICAgICAgSk9JTiBhY2NvdW50cyBhCiAgICAgICAgICAgICAgT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCiAgICAgICAgICAgICAgSk9JTiBvcmRlcnMgbwogICAgICAgICAgICAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgICAgICAgICAgICBKT0lOIHJlZ2lvbiByCiAgICAgICAgICAgICAgT04gci5pZCA9IHMucmVnaW9uX2lkCiAgICAgICAgICAgICAgR1JPVVAgQlkgci5uYW1lKSBzdWIpOwpgYGAKMy4gRm9yIHRoZSBuYW1lIG9mIHRoZSBhY2NvdW50IHRoYXQgcHVyY2hhc2VkIHRoZSBtb3N0IChpbiB0b3RhbCBvdmVyIHRoZWlyIGxpZmV0aW1lIGFzIGEgY3VzdG9tZXIpIHN0YW5kYXJkX3F0eSBwYXBlciwgaG93IG1hbnkgYWNjb3VudHMgc3RpbGwgaGFkIG1vcmUgaW4gdG90YWwgcHVyY2hhc2VzPyAKRmlyc3QsIHdlIHdhbnQgdG8gZmluZCB0aGUgYWNjb3VudCB0aGF0IGhhZCB0aGUgbW9zdCBzdGFuZGFyZF9xdHkgcGFwZXIuIFRoZSBxdWVyeSBoZXJlIHB1bGxzIHRoYXQgYWNjb3VudCwgYXMgd2VsbCBhcyB0aGUgdG90YWwgYW1vdW50OgpgYGB7c3FsfQpTRUxFQ1QgYS5uYW1lIGFjY291bnRfbmFtZSwgU1VNKG8uc3RhbmRhcmRfcXR5KSB0b3RhbF9zdGQsIFNVTShvLnRvdGFsKSB0b3RhbApGUk9NIGFjY291bnRzIGEKSk9JTiBvcmRlcnMgbwpPTiBvLmFjY291bnRfaWQgPSBhLmlkCkdST1VQIEJZIDEKT1JERVIgQlkgMiBERVNDCkxJTUlUIDE7CmBgYApOb3csIEkgd2FudCB0byB1c2UgdGhpcyB0byBwdWxsIGFsbCB0aGUgYWNjb3VudHMgd2l0aCBtb3JlIHRvdGFsIHNhbGVzOgpgYGB7c3FsfQpTRUxFQ1QgYS5uYW1lCkZST00gb3JkZXJzIG8KSk9JTiBhY2NvdW50cyBhCk9OIGEuaWQgPSBvLmFjY291bnRfaWQKR1JPVVAgQlkgMQpIQVZJTkcgU1VNKG8udG90YWwpID4gKFNFTEVDVCB0b3RhbCAKICAgICAgICAgICAgICAgICAgRlJPTSAoU0VMRUNUIGEubmFtZSBhY3RfbmFtZSwgU1VNKG8uc3RhbmRhcmRfcXR5KSB0b3Rfc3RkLCBTVU0oby50b3RhbCkgdG90YWwKICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBhY2NvdW50cyBhCiAgICAgICAgICAgICAgICAgICAgICAgIEpPSU4gb3JkZXJzIG8KICAgICAgICAgICAgICAgICAgICAgICAgT04gby5hY2NvdW50X2lkID0gYS5pZAogICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSAxCiAgICAgICAgICAgICAgICAgICAgICAgIE9SREVSIEJZIDIgREVTQwogICAgICAgICAgICAgICAgICAgICAgICBMSU1JVCAxKSBzdWIpOwpgYGAKVGhpcyBpcyBub3cgYSBsaXN0IG9mIGFsbCB0aGUgYWNjb3VudHMgd2l0aCBtb3JlIHRvdGFsIG9yZGVycy4gV2UgY2FuIGdldCB0aGUgY291bnQgd2l0aCBqdXN0IGFub3RoZXIgc2ltcGxlIHN1YnF1ZXJ5LgpgYGB7c3FsfQpTRUxFQ1QgQ09VTlQoKikKRlJPTSAoU0VMRUNUIGEubmFtZQogICAgICBGUk9NIG9yZGVycyBvCiAgICAgIEpPSU4gYWNjb3VudHMgYQogICAgICBPTiBhLmlkID0gby5hY2NvdW50X2lkCiAgICAgIEdST1VQIEJZIDEKICAgICAgSEFWSU5HIFNVTShvLnRvdGFsKSA+IChTRUxFQ1QgdG90YWwgCiAgICAgICAgICAgICAgICAgIEZST00gKFNFTEVDVCBhLm5hbWUgYWN0X25hbWUsIFNVTShvLnN0YW5kYXJkX3F0eSkgdG90X3N0ZCwgU1VNKG8udG90YWwpIHRvdGFsCiAgICAgICAgICAgICAgICAgICAgICAgIEZST00gYWNjb3VudHMgYQogICAgICAgICAgICAgICAgICAgICAgICBKT0lOIG9yZGVycyBvCiAgICAgICAgICAgICAgICAgICAgICAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgMQogICAgICAgICAgICAgICAgICAgICAgICBPUkRFUiBCWSAyIERFU0MKICAgICAgICAgICAgICAgICAgICAgICAgTElNSVQgMSkgaW5uZXJfdGFiKQogICAgICAgICAgICApIGNvdW50ZXJfdGFiOwpgYGAKNC4gRm9yIHRoZSBjdXN0b21lciB0aGF0IHNwZW50IHRoZSBtb3N0IChpbiB0b3RhbCBvdmVyIHRoZWlyIGxpZmV0aW1lIGFzIGEgY3VzdG9tZXIpIHRvdGFsX2FtdF91c2QsIGhvdyBtYW55IHdlYl9ldmVudHMgZGlkIHRoZXkgaGF2ZSBmb3IgZWFjaCBjaGFubmVsPwpIZXJlLCB3ZSBmaXJzdCB3YW50IHRvIHB1bGwgdGhlIGN1c3RvbWVyIHdpdGggdGhlIG1vc3Qgc3BlbnQgaW4gbGlmZXRpbWUgdmFsdWUuCmBgYHtzcWx9ClNFTEVDVCBhLmlkLCBhLm5hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdF9zcGVudApGUk9NIG9yZGVycyBvCkpPSU4gYWNjb3VudHMgYQpPTiBhLmlkID0gby5hY2NvdW50X2lkCkdST1VQIEJZIGEuaWQsIGEubmFtZQpPUkRFUiBCWSAzIERFU0MKTElNSVQgMTsKYGBgCk5vdywgd2Ugd2FudCB0byBsb29rIGF0IHRoZSBudW1iZXIgb2YgZXZlbnRzIG9uIGVhY2ggY2hhbm5lbCB0aGlzIGNvbXBhbnkgaGFkLCB3aGljaCB3ZSBjYW4gbWF0Y2ggd2l0aCBqdXN0IHRoZSBpZC4KYGBge3NxbH0KU0VMRUNUIGEubmFtZSwgdy5jaGFubmVsLCBDT1VOVCgqKQpGUk9NIGFjY291bnRzIGEKSk9JTiB3ZWJfZXZlbnRzIHcKT04gYS5pZCA9IHcuYWNjb3VudF9pZCBBTkQgYS5pZCA9ICAoU0VMRUNUIGlkCiAgICAgICAgICAgICAgICAgICAgIEZST00gKFNFTEVDVCBhLmlkLCBhLm5hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdF9zcGVudAogICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIG9yZGVycyBvCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEpPSU4gYWNjb3VudHMgYQogICAgICAgICAgICAgICAgICAgICAgICAgICBPTiBhLmlkID0gby5hY2NvdW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIGEuaWQsIGEubmFtZQogICAgICAgICAgICAgICAgICAgICAgICAgICBPUkRFUiBCWSAzIERFU0MKICAgICAgICAgICAgICAgICAgICAgICAgICAgTElNSVQgMSkgaW5uZXJfdGFibGUpCkdST1VQIEJZIDEsIDIKT1JERVIgQlkgMyBERVNDOwpgYGAKSSBhZGRlZCBhbiBPUkRFUiBCWSBmb3Igbm8gcmVhbCByZWFzb24sIGFuZCB0aGUgYWNjb3VudCBuYW1lIHRvIGFzc3VyZSBJIHdhcyBvbmx5IHB1bGxpbmcgZnJvbSBvbmUgYWNjb3VudC4KNS4gV2hhdCBpcyB0aGUgbGlmZXRpbWUgYXZlcmFnZSBhbW91bnQgc3BlbnQgaW4gdGVybXMgb2YgdG90YWxfYW10X3VzZCBmb3IgdGhlIHRvcCAxMCB0b3RhbCBzcGVuZGluZyBhY2NvdW50cz8KRmlyc3QsIHdlIGp1c3Qgd2FudCB0byBmaW5kIHRoZSB0b3AgMTAgYWNjb3VudHMgaW4gdGVybXMgb2YgaGlnaGVzdCB0b3RhbF9hbXRfdXNkLgpgYGB7c3FsfQpTRUxFQ1QgYS5pZCwgYS5uYW1lLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3Rfc3BlbnQKRlJPTSBvcmRlcnMgbwpKT0lOIGFjY291bnRzIGEKT04gYS5pZCA9IG8uYWNjb3VudF9pZApHUk9VUCBCWSBhLmlkLCBhLm5hbWUKT1JERVIgQlkgMyBERVNDCkxJTUlUIDEwOwpgYGAKTm93LCB3ZSBqdXN0IHdhbnQgdGhlIGF2ZXJhZ2Ugb2YgdGhlc2UgMTAgYW1vdW50cy4KYGBge3NxbH0KU0VMRUNUIEFWRyh0b3Rfc3BlbnQpCkZST00gKFNFTEVDVCBhLmlkLCBhLm5hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdF9zcGVudAogICAgICBGUk9NIG9yZGVycyBvCiAgICAgIEpPSU4gYWNjb3VudHMgYQogICAgICBPTiBhLmlkID0gby5hY2NvdW50X2lkCiAgICAgIEdST1VQIEJZIGEuaWQsIGEubmFtZQogICAgICBPUkRFUiBCWSAzIERFU0MKICAgICAgIExJTUlUIDEwKSB0ZW1wOwpgYGAKNi4gV2hhdCBpcyB0aGUgbGlmZXRpbWUgYXZlcmFnZSBhbW91bnQgc3BlbnQgaW4gdGVybXMgb2YgdG90YWxfYW10X3VzZCBmb3Igb25seSB0aGUgY29tcGFuaWVzIHRoYXQgc3BlbnQgbW9yZSB0aGFuIHRoZSBhdmVyYWdlIG9mIGFsbCBvcmRlcnMuCkZpcnN0LCB3ZSB3YW50IHRvIHB1bGwgdGhlIGF2ZXJhZ2Ugb2YgYWxsIGFjY291bnRzIGluIHRlcm1zIG9mIHRvdGFsX2FtdF91c2Q6CmBgYHtzcWx9ClNFTEVDVCBBVkcoby50b3RhbF9hbXRfdXNkKSBhdmdfYWxsCkZST00gb3JkZXJzIG8KSk9JTiBhY2NvdW50cyBhCk9OIGEuaWQgPSBvLmFjY291bnRfaWQ7CmBgYApUaGVuLCB3ZSB3YW50IHRvIG9ubHkgcHVsbCB0aGUgYWNjb3VudHMgd2l0aCBtb3JlIHRoYW4gdGhpcyBhdmVyYWdlIGFtb3VudC4KYGBge3NxbH0KU0VMRUNUIG8uYWNjb3VudF9pZCwgQVZHKG8udG90YWxfYW10X3VzZCkKRlJPTSBvcmRlcnMgbwpHUk9VUCBCWSAxCkhBVklORyBBVkcoby50b3RhbF9hbXRfdXNkKSA+IChTRUxFQ1QgQVZHKG8udG90YWxfYW10X3VzZCkgYXZnX2FsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBvcmRlcnMgbwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSk9JTiBhY2NvdW50cyBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPTiBhLmlkID0gby5hY2NvdW50X2lkKTsKYGBgCkZpbmFsbHksIHdlIGp1c3Qgd2FudCB0aGUgYXZlcmFnZSBvZiB0aGVzZSB2YWx1ZXMuCmBgYHtzcWx9ClNFTEVDVCBBVkcoYXZnX2FtdCkKRlJPTSAoU0VMRUNUIG8uYWNjb3VudF9pZCwgQVZHKG8udG90YWxfYW10X3VzZCkgYXZnX2FtdAogICAgRlJPTSBvcmRlcnMgbwogICAgR1JPVVAgQlkgMQogICAgSEFWSU5HIEFWRyhvLnRvdGFsX2FtdF91c2QpID4gKFNFTEVDVCBBVkcoby50b3RhbF9hbXRfdXNkKSBhdmdfYWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIG9yZGVycyBvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBKT0lOIGFjY291bnRzIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9OIGEuaWQgPSBvLmFjY291bnRfaWQpKSB0ZW1wX3RhYmxlOwpgYGAKCioqKgoKIyBXSVRIClRoZSBXSVRIIHN0YXRlbWVudCBpcyBvZnRlbiBjYWxsZWQgYSBDb21tb24gVGFibGUgRXhwcmVzc2lvbiBvciBDVEUuIFRob3VnaCB0aGVzZSBleHByZXNzaW9ucyBzZXJ2ZSB0aGUgZXhhY3Qgc2FtZSBwdXJwb3NlIGFzIHN1YnF1ZXJpZXMsIHRoZXkgYXJlIG1vcmUgY29tbW9uIGluIHByYWN0aWNlLCBhcyB0aGV5IHRlbmQgdG8gYmUgY2xlYW5lciBmb3IgYSBmdXR1cmUgcmVhZGVyIHRvIGZvbGxvdyB0aGUgbG9naWMuClFVRVNUSU9OOiBZb3UgbmVlZCB0byBmaW5kIHRoZSBhdmVyYWdlIG51bWJlciBvZiBldmVudHMgZm9yIGVhY2ggY2hhbm5lbCBwZXIgZGF5ClVzaW5nIHN1YnF1ZXJpZXM6CmBgYHtzcWx9ClNFTEVDVCBjaGFubmVsLCBBVkcoZXZlbnRzKSBBUyBhdmVyYWdlX2V2ZW50cwpGUk9NIChTRUxFQ1QgREFURV9UUlVOQygnZGF5JyxvY2N1cnJlZF9hdCkgQVMgZGF5LAogICAgICAgICAgICAgY2hhbm5lbCwgQ09VTlQoKikgYXMgZXZlbnRzCiAgICAgIEZST00gd2ViX2V2ZW50cyAKICAgICAgR1JPVVAgQlkgMSwyKSBzdWIKR1JPVVAgQlkgY2hhbm5lbApPUkRFUiBCWSAyIERFU0M7CmBgYApMZXQncyB0cnkgdGhpcyBhZ2FpbiB1c2luZyBhIFdJVEggc3RhdGVtZW50LgpOb3RpY2UsIHlvdSBjYW4gcHVsbCB0aGUgaW5uZXIgcXVlcnk6CmBgYHtzcWx9ClNFTEVDVCBEQVRFX1RSVU5DKCdkYXknLG9jY3VycmVkX2F0KSBBUyBkYXksIAogICAgICAgY2hhbm5lbCwgQ09VTlQoKikgYXMgZXZlbnRzCkZST00gd2ViX2V2ZW50cyAKR1JPVVAgQlkgMSwyCmBgYApUaGlzIGlzIHRoZSBwYXJ0IHdlIHB1dCBpbiB0aGUgV0lUSCBzdGF0ZW1lbnQuIE5vdGljZSwgd2UgYXJlIGFsaWFzaW5nIHRoZSB0YWJsZSBhcyBldmVudHMgYmVsb3c6CmBgYHtzcWx9CldJVEggZXZlbnRzIEFTICgKICAgICAgICAgIFNFTEVDVCBEQVRFX1RSVU5DKCdkYXknLG9jY3VycmVkX2F0KSBBUyBkYXksIAogICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsLCBDT1VOVCgqKSBhcyBldmVudHMKICAgICAgICAgIEZST00gd2ViX2V2ZW50cyAKICAgICAgICAgIEdST1VQIEJZIDEsMikKYGBgCk5vdywgd2UgY2FuIHVzZSB0aGlzIG5ld2x5IGNyZWF0ZWQgZXZlbnRzIHRhYmxlIGFzIGlmIGl0IGlzIGFueSBvdGhlciB0YWJsZSBpbiBvdXIgZGF0YWJhc2U6CmBgYHtzcWx9CldJVEggZXZlbnRzIEFTICgKICAgICAgICAgIFNFTEVDVCBEQVRFX1RSVU5DKCdkYXknLG9jY3VycmVkX2F0KSBBUyBkYXksIAogICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsLCBDT1VOVCgqKSBhcyBldmVudHMKICAgICAgICAgIEZST00gd2ViX2V2ZW50cyAKICAgICAgICAgIEdST1VQIEJZIDEsMikKClNFTEVDVCBjaGFubmVsLCBBVkcoZXZlbnRzKSBBUyBhdmVyYWdlX2V2ZW50cwpGUk9NIGV2ZW50cwpHUk9VUCBCWSBjaGFubmVsCk9SREVSIEJZIDIgREVTQzsKYGBgCkZvciB0aGUgYWJvdmUgZXhhbXBsZSwgd2UgZG9uJ3QgbmVlZCBhbnltb3JlIHRoYW4gdGhlIG9uZSBhZGRpdGlvbmFsIHRhYmxlLCBidXQgaW1hZ2luZSB3ZSBuZWVkZWQgdG8gY3JlYXRlIGEgc2Vjb25kIHRhYmxlIHRvIHB1bGwgZnJvbS4gV2UgY2FuIGNyZWF0ZSBhbiBhZGRpdGlvbmFsIHRhYmxlIHRvIHB1bGwgZnJvbSBpbiB0aGUgZm9sbG93aW5nIHdheToKYGBge3NxbH0KV0lUSCB0YWJsZTEgQVMgKAogICAgICAgICAgU0VMRUNUICoKICAgICAgICAgIEZST00gd2ViX2V2ZW50cyksCgogICAgIHRhYmxlMiBBUyAoCiAgICAgICAgICBTRUxFQ1QgKgogICAgICAgICAgRlJPTSBhY2NvdW50cykKCgpTRUxFQ1QgKgpGUk9NIHRhYmxlMQpKT0lOIHRhYmxlMgpPTiB0YWJsZTEuYWNjb3VudF9pZCA9IHRhYmxlMi5pZDsKYGBgCllvdSBjYW4gYWRkIG1vcmUgYW5kIG1vcmUgdGFibGVzIHVzaW5nIHRoZSBXSVRIIHN0YXRlbWVudCBpbiB0aGUgc2FtZSB3YXkuCgpFc3NlbnRpYWxseSBhIFdJVEggc3RhdGVtZW50IHBlcmZvcm1zIHRoZSBzYW1lIHRhc2sgYXMgYSBTdWJxdWVyeS4gVGhlcmVmb3JlLCB5b3UgY2FuIHdyaXRlIGFueSBvZiB0aGUgcXVlcmllcyB3ZSB3b3JrZWQgd2l0aCBpbiB0aGUgcHJldmlvdXMgc2V0IG9mIHF1ZXN0aW9ucyB1c2luZyBhIFdJVEguIFRoYXQncyB3aGF0IHlvdSdsbCBkbyBoZXJlLiBUcnkgdG8gcGVyZm9ybSBlYWNoIG9mIHRoZSBlYXJsaWVyIHF1ZXJpZXMgYWdhaW4sIGJ1dCB1c2luZyBhIFdJVEggaW5zdGVhZCBvZiBhIHN1YnF1ZXJ5LgoKIyMjIFF1ZXN0aW9ucwoxLiBQcm92aWRlIHRoZSBuYW1lIG9mIHRoZSBzYWxlc19yZXAgaW4gZWFjaCByZWdpb24gd2l0aCB0aGUgbGFyZ2VzdCBhbW91bnQgb2YgdG90YWxfYW10X3VzZCBzYWxlcy4KYGBge3NxbH0KV0lUSCB0MSBBUyAoCiAgU0VMRUNUIHMubmFtZSByZXBfbmFtZSwgci5uYW1lIHJlZ2lvbl9uYW1lLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3RhbF9hbXQKICAgRlJPTSBzYWxlc19yZXBzIHMKICAgSk9JTiBhY2NvdW50cyBhCiAgIE9OIGEuc2FsZXNfcmVwX2lkID0gcy5pZAogICBKT0lOIG9yZGVycyBvCiAgIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICAgSk9JTiByZWdpb24gcgogICBPTiByLmlkID0gcy5yZWdpb25faWQKICAgR1JPVVAgQlkgMSwyCiAgIE9SREVSIEJZIDMgREVTQyksIAp0MiBBUyAoCiAgIFNFTEVDVCByZWdpb25fbmFtZSwgTUFYKHRvdGFsX2FtdCkgdG90YWxfYW10CiAgIEZST00gdDEKICAgR1JPVVAgQlkgMSkKU0VMRUNUIHQxLnJlcF9uYW1lLCB0MS5yZWdpb25fbmFtZSwgdDEudG90YWxfYW10CkZST00gdDEKSk9JTiB0MgpPTiB0MS5yZWdpb25fbmFtZSA9IHQyLnJlZ2lvbl9uYW1lIEFORCB0MS50b3RhbF9hbXQgPSB0Mi50b3RhbF9hbXQ7ICAKYGBgCgoyLkZvciB0aGUgcmVnaW9uIHdpdGggdGhlIGxhcmdlc3Qgc2FsZXMgdG90YWxfYW10X3VzZCwgaG93IG1hbnkgdG90YWwgb3JkZXJzIHdlcmUgcGxhY2VkPyAKYGBge3NxbH0KV0lUSCB0MSBBUyAoCiAgIFNFTEVDVCByLm5hbWUgcmVnaW9uX25hbWUsIFNVTShvLnRvdGFsX2FtdF91c2QpIHRvdGFsX2FtdAogICBGUk9NIHNhbGVzX3JlcHMgcwogICBKT0lOIGFjY291bnRzIGEKICAgT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCiAgIEpPSU4gb3JkZXJzIG8KICAgT04gby5hY2NvdW50X2lkID0gYS5pZAogICBKT0lOIHJlZ2lvbiByCiAgIE9OIHIuaWQgPSBzLnJlZ2lvbl9pZAogICBHUk9VUCBCWSByLm5hbWUpLCAKdDIgQVMgKAogICBTRUxFQ1QgTUFYKHRvdGFsX2FtdCkKICAgRlJPTSB0MSkKU0VMRUNUIHIubmFtZSwgU1VNKG8udG90YWwpIHRvdGFsX29yZGVycwpGUk9NIHNhbGVzX3JlcHMgcwpKT0lOIGFjY291bnRzIGEKT04gYS5zYWxlc19yZXBfaWQgPSBzLmlkCkpPSU4gb3JkZXJzIG8KT04gby5hY2NvdW50X2lkID0gYS5pZApKT0lOIHJlZ2lvbiByCk9OIHIuaWQgPSBzLnJlZ2lvbl9pZApHUk9VUCBCWSByLm5hbWUKSEFWSU5HIFNVTShvLnRvdGFsX2FtdF91c2QpID0gKFNFTEVDVCAqIEZST00gdDIpOwpgYGAKCjMuIEZvciB0aGUgbmFtZSBvZiB0aGUgYWNjb3VudCB0aGF0IHB1cmNoYXNlZCB0aGUgbW9zdCAoaW4gdG90YWwgb3ZlciB0aGVpciBsaWZldGltZSBhcyBhIGN1c3RvbWVyKSBzdGFuZGFyZF9xdHkgcGFwZXIsIGhvdyBtYW55IGFjY291bnRzIHN0aWxsIGhhZCBtb3JlIGluIHRvdGFsIHB1cmNoYXNlcz8gCmBgYHtzcWx9CldJVEggdDEgQVMgKAogIFNFTEVDVCBhLm5hbWUgYWNjb3VudF9uYW1lLCBTVU0oby5zdGFuZGFyZF9xdHkpIHRvdGFsX3N0ZCwgU1VNKG8udG90YWwpIHRvdGFsCiAgRlJPTSBhY2NvdW50cyBhCiAgSk9JTiBvcmRlcnMgbwogIE9OIG8uYWNjb3VudF9pZCA9IGEuaWQKICBHUk9VUCBCWSAxCiAgT1JERVIgQlkgMiBERVNDCiAgTElNSVQgMSksIAp0MiBBUyAoCiAgU0VMRUNUIGEubmFtZQogIEZST00gb3JkZXJzIG8KICBKT0lOIGFjY291bnRzIGEKICBPTiBhLmlkID0gby5hY2NvdW50X2lkCiAgR1JPVVAgQlkgMQogIEhBVklORyBTVU0oby50b3RhbCkgPiAoU0VMRUNUIHRvdGFsIEZST00gdDEpKQpTRUxFQ1QgQ09VTlQoKikKRlJPTSB0MjsKYGBgCgo0LiBGb3IgdGhlIGN1c3RvbWVyIHRoYXQgc3BlbnQgdGhlIG1vc3QgKGluIHRvdGFsIG92ZXIgdGhlaXIgbGlmZXRpbWUgYXMgYSBjdXN0b21lcikgdG90YWxfYW10X3VzZCwgaG93IG1hbnkgd2ViX2V2ZW50cyBkaWQgdGhleSBoYXZlIGZvciBlYWNoIGNoYW5uZWw/CmBgYHtzcWx9CldJVEggdDEgQVMgKAogICBTRUxFQ1QgYS5pZCwgYS5uYW1lLCBTVU0oby50b3RhbF9hbXRfdXNkKSB0b3Rfc3BlbnQKICAgRlJPTSBvcmRlcnMgbwogICBKT0lOIGFjY291bnRzIGEKICAgT04gYS5pZCA9IG8uYWNjb3VudF9pZAogICBHUk9VUCBCWSBhLmlkLCBhLm5hbWUKICAgT1JERVIgQlkgMyBERVNDCiAgIExJTUlUIDEpClNFTEVDVCBhLm5hbWUsIHcuY2hhbm5lbCwgQ09VTlQoKikKRlJPTSBhY2NvdW50cyBhCkpPSU4gd2ViX2V2ZW50cyB3Ck9OIGEuaWQgPSB3LmFjY291bnRfaWQgQU5EIGEuaWQgPSAgKFNFTEVDVCBpZCBGUk9NIHQxKQpHUk9VUCBCWSAxLCAyCk9SREVSIEJZIDMgREVTQzsKYGBgCgo1LiBXaGF0IGlzIHRoZSBsaWZldGltZSBhdmVyYWdlIGFtb3VudCBzcGVudCBpbiB0ZXJtcyBvZiB0b3RhbF9hbXRfdXNkIGZvciB0aGUgdG9wIDEwIHRvdGFsIHNwZW5kaW5nIGFjY291bnRzPwpgYGB7c3FsfQpXSVRIIHQxIEFTICgKICAgU0VMRUNUIGEuaWQsIGEubmFtZSwgU1VNKG8udG90YWxfYW10X3VzZCkgdG90X3NwZW50CiAgIEZST00gb3JkZXJzIG8KICAgSk9JTiBhY2NvdW50cyBhCiAgIE9OIGEuaWQgPSBvLmFjY291bnRfaWQKICAgR1JPVVAgQlkgYS5pZCwgYS5uYW1lCiAgIE9SREVSIEJZIDMgREVTQwogICBMSU1JVCAxMCkKU0VMRUNUIEFWRyh0b3Rfc3BlbnQpCkZST00gdDE7CmBgYAoKNi4gV2hhdCBpcyB0aGUgbGlmZXRpbWUgYXZlcmFnZSBhbW91bnQgc3BlbnQgaW4gdGVybXMgb2YgdG90YWxfYW10X3VzZCBmb3Igb25seSB0aGUgY29tcGFuaWVzIHRoYXQgc3BlbnQgbW9yZSB0aGFuIHRoZSBhdmVyYWdlIG9mIGFsbCBvcmRlcnMuCmBgYHtzcWx9CldJVEggdDEgQVMgKAogICBTRUxFQ1QgQVZHKG8udG90YWxfYW10X3VzZCkgYXZnX2FsbAogICBGUk9NIG9yZGVycyBvCiAgIEpPSU4gYWNjb3VudHMgYQogICBPTiBhLmlkID0gby5hY2NvdW50X2lkKSwKdDIgQVMgKAogICBTRUxFQ1Qgby5hY2NvdW50X2lkLCBBVkcoby50b3RhbF9hbXRfdXNkKSBhdmdfYW10CiAgIEZST00gb3JkZXJzIG8KICAgR1JPVVAgQlkgMQogICBIQVZJTkcgQVZHKG8udG90YWxfYW10X3VzZCkgPiAoU0VMRUNUICogRlJPTSB0MSkpClNFTEVDVCBBVkcoYXZnX2FtdCkKRlJPTSB0MjsKYGBgCgoqKioKIyBTUUwgRGF0YSBDbGVhbmluZwoKIyMgTEVGVCwgUklHSFQsIExFTkdUSAoqTEVGVCogcHVsbHMgYSBzcGVjaWZpZWQgbnVtYmVyIG9mIGNoYXJhY3RlcnMgZm9yIGVhY2ggcm93IGluIGEgc3BlY2lmaWVkIGNvbHVtbiBzdGFydGluZyBhdCB0aGUgYmVnaW5uaW5nIChvciBmcm9tIHRoZSBsZWZ0KS4gRm9yIGV4YW1wbGUsIHlvdSBjYW4gcHVsbCB0aGUgZmlyc3QgdGhyZWUgZGlnaXRzIG9mIGEgcGhvbmUgbnVtYmVyIHVzaW5nIExFRlQocGhvbmVfbnVtYmVyLCAzKS4KCipSSUdIVCogcHVsbHMgYSBzcGVjaWZpZWQgbnVtYmVyIG9mIGNoYXJhY3RlcnMgZm9yIGVhY2ggcm93IGluIGEgc3BlY2lmaWVkIGNvbHVtbiBzdGFydGluZyBhdCB0aGUgZW5kIChvciBmcm9tIHRoZSByaWdodCkuIEZvciBleGFtcGxlLCB5b3UgY2FuIHB1bGwgdGhlIGxhc3QgZWlnaHQgZGlnaXRzIG9mIGEgcGhvbmUgbnVtYmVyIHVzaW5nIFJJR0hUKHBob25lX251bWJlciwgOCkuCgoqTEVOR1RIKiBwcm92aWRlcyB0aGUgbnVtYmVyIG9mIGNoYXJhY3RlcnMgZm9yIGVhY2ggcm93IG9mIGEgc3BlY2lmaWVkIGNvbHVtbi4gWW91IGNvdWxkIHVzZSB0aGlzIHRvIGdldCB0aGUgbGVuZ3RoIG9mIGVhY2ggcGhvbmUgbnVtYmVyIGFzIExFTkdUSChwaG9uZV9udW1iZXIpLgoKIyMjIFF1ZXN0aW9ucwoxLiBJbiB0aGUgYWNjb3VudHMgdGFibGUsIHRoZXJlIGlzIGEgY29sdW1uIGhvbGRpbmcgdGhlIHdlYnNpdGUgZm9yIGVhY2ggY29tcGFueS4gVGhlIGxhc3QgdGhyZWUgZGlnaXRzIHNwZWNpZnkgd2hhdCB0eXBlIG9mIHdlYiBhZGRyZXNzIHRoZXkgYXJlIHVzaW5nLiBBIGxpc3Qgb2YgZXh0ZW5zaW9ucyAoYW5kIHByaWNpbmcpIGlzIHByb3ZpZGVkLiBQdWxsIHRoZXNlIGV4dGVuc2lvbnMgYW5kIHByb3ZpZGUgaG93IG1hbnkgb2YgZWFjaCB3ZWJzaXRlIHR5cGUgZXhpc3QgaW4gdGhlIGFjY291bnRzIHRhYmxlLgpgYGB7c3FsfQpTRUxFQ1QgUklHSFQod2Vic2l0ZSwgMykgQVMgZG9tYWluLCBDT1VOVCgqKSBudW1fY29tcGFuaWVzCkZST00gYWNjb3VudHMKR1JPVVAgQlkgMQpPUkRFUiBCWSAyIERFU0M7CmBgYAoKCjIuIFRoZXJlIGlzIG11Y2ggZGViYXRlIGFib3V0IGhvdyBtdWNoIHRoZSBuYW1lIChvciBldmVuIHRoZSBmaXJzdCBsZXR0ZXIgb2YgYSBjb21wYW55IG5hbWUpIG1hdHRlcnMuIFVzZSB0aGUgYWNjb3VudHMgdGFibGUgdG8gcHVsbCB0aGUgZmlyc3QgbGV0dGVyIG9mIGVhY2ggY29tcGFueSBuYW1lIHRvIHNlZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGNvbXBhbnkgbmFtZXMgdGhhdCBiZWdpbiB3aXRoIGVhY2ggbGV0dGVyIChvciBudW1iZXIpLiAKYGBge3NxbH0KU0VMRUNUIExFRlQoVVBQRVIobmFtZSksIDEpIEFTIGZpcnN0X2xldHRlciwgQ09VTlQoKikgbnVtX2NvbXBhbmllcwpGUk9NIGFjY291bnRzCkdST1VQIEJZIDEKT1JERVIgQlkgMiBERVNDOwpgYGAKCjMuIFVzZSB0aGUgYWNjb3VudHMgdGFibGUgYW5kIGEgQ0FTRSBzdGF0ZW1lbnQgdG8gY3JlYXRlIHR3byBncm91cHM6IG9uZSBncm91cCBvZiBjb21wYW55IG5hbWVzIHRoYXQgc3RhcnQgd2l0aCBhIG51bWJlciBhbmQgYSBzZWNvbmQgZ3JvdXAgb2YgdGhvc2UgY29tcGFueSBuYW1lcyB0aGF0IHN0YXJ0IHdpdGggYSBsZXR0ZXIuIFdoYXQgcHJvcG9ydGlvbiBvZiBjb21wYW55IG5hbWVzIHN0YXJ0IHdpdGggYSBsZXR0ZXI/CmBgYHtzcWx9ClNFTEVDVCBTVU0obnVtKSBudW1zLCBTVU0obGV0dGVyKSBsZXR0ZXJzCkZST00gKFNFTEVDVCBuYW1lLCBDQVNFIFdIRU4gTEVGVChVUFBFUihuYW1lKSwgMSkgSU4gKCcwJywnMScsJzInLCczJywnNCcsJzUnLCc2JywnNycsJzgnLCc5JykgCiAgICAgICAgICAgICAgICAgICAgICAgVEhFTiAxIEVMU0UgMCBFTkQgQVMgbnVtLCAKICAgICAgICAgQ0FTRSBXSEVOIExFRlQoVVBQRVIobmFtZSksIDEpIElOICgnMCcsJzEnLCcyJywnMycsJzQnLCc1JywnNicsJzcnLCc4JywnOScpIAogICAgICAgICAgICAgICAgICAgICAgIFRIRU4gMCBFTFNFIDEgRU5EIEFTIGxldHRlcgogICAgICBGUk9NIGFjY291bnRzKSB0MTsKYGBgClRoZXJlIGFyZSAzNTAgY29tcGFueSBuYW1lcyB0aGF0IHN0YXJ0IHdpdGggYSBsZXR0ZXIgYW5kIDEgdGhhdCBzdGFydHMgd2l0aCBhIG51bWJlci4gVGhpcyBnaXZlcyBhIHJhdGlvIG9mIDM1MC8zNTEgdGhhdCBhcmUgY29tcGFueSBuYW1lcyB0aGF0IHN0YXJ0IHdpdGggYSBsZXR0ZXIgb3IgOTkuNyUuCgo0LiBDb25zaWRlciB2b3dlbHMgYXMgYSwgZSwgaSwgbywgYW5kIHUuIFdoYXQgcHJvcG9ydGlvbiBvZiBjb21wYW55IG5hbWVzIHN0YXJ0IHdpdGggYSB2b3dlbCwgYW5kIHdoYXQgcGVyY2VudCBzdGFydCB3aXRoIGFueXRoaW5nIGVsc2U/CmBgYHtzcWx9ClNFTEVDVCBTVU0odm93ZWxzKSB2b3dlbHMsIFNVTShvdGhlcikgb3RoZXIKRlJPTSAoU0VMRUNUIG5hbWUsIENBU0UgV0hFTiBMRUZUKFVQUEVSKG5hbWUpLCAxKSBJTiAoJ0EnLCdFJywnSScsJ08nLCdVJykgCiAgICAgICAgICAgICAgICAgICAgICAgIFRIRU4gMSBFTFNFIDAgRU5EIEFTIHZvd2VscywgCiAgICAgICAgICBDQVNFIFdIRU4gTEVGVChVUFBFUihuYW1lKSwgMSkgSU4gKCdBJywnRScsJ0knLCdPJywnVScpIAogICAgICAgICAgICAgICAgICAgICAgIFRIRU4gMCBFTFNFIDEgRU5EIEFTIG90aGVyCiAgICAgICAgIEZST00gYWNjb3VudHMpIHQxOwpgYGAKVGhlcmUgYXJlIDgwIGNvbXBhbnkgbmFtZXMgdGhhdCBzdGFydCB3aXRoIGEgdm93ZWwgYW5kIDI3MSB0aGF0IHN0YXJ0IHdpdGggb3RoZXIgY2hhcmFjdGVycy4gVGhlcmVmb3JlIDgwLzM1MSBhcmUgdm93ZWxzIG9yIDIyLjglLiBUaGVyZWZvcmUsIDc3LjIlIG9mIGNvbXBhbnkgbmFtZXMgZG8gbm90IHN0YXJ0IHdpdGggdm93ZWxzLgoKIyMgUE9TSVRJT04sIFNUUlBPUywgTE9XRVIsIFVQUEVSCipQT1NJVElPTiogdGFrZXMgYSBjaGFyYWN0ZXIgYW5kIGEgY29sdW1uLCBhbmQgcHJvdmlkZXMgdGhlIGluZGV4IHdoZXJlIHRoYXQgY2hhcmFjdGVyIGlzIGZvciBlYWNoIHJvdy4gVGhlIGluZGV4IG9mIHRoZSBmaXJzdCBwb3NpdGlvbiBpcyAxIGluIFNRTC4gSGVyZSwgeW91IHNhdyB0aGF0IHlvdSBjYW4gcHVsbCB0aGUgaW5kZXggb2YgYSBjb21tYSBhcyBQT1NJVElPTignLCcgSU4gY2l0eV9zdGF0ZSkuCgoqU1RSUE9TKiBwcm92aWRlcyB0aGUgc2FtZSByZXN1bHQgYXMgUE9TSVRJT04sIGJ1dCB0aGUgc3ludGF4IGZvciBhY2hpZXZpbmcgdGhvc2UgcmVzdWx0cyBpcyBhIGJpdCBkaWZmZXJlbnQgYXMgc2hvd24gaGVyZTogU1RSUE9TKGNpdHlfc3RhdGUsICcsJykuCgoqTm90ZSosIGJvdGggUE9TSVRJT04gYW5kIFNUUlBPUyBhcmUgKmNhc2Ugc2Vuc2l0aXZlKiwgc28gbG9va2luZyBmb3IgQSBpcyBkaWZmZXJlbnQgdGhhbiBsb29raW5nIGZvciBhLiAKVGhlcmVmb3JlLCBpZiB5b3Ugd2FudCB0byBwdWxsIGFuIGluZGV4IHJlZ2FyZGxlc3Mgb2YgdGhlIGNhc2Ugb2YgYSBsZXR0ZXIsIHlvdSBtaWdodCB3YW50IHRvIHVzZSAqTE9XRVIqIG9yICpVUFBFUiogdG8gbWFrZSBhbGwgb2YgdGhlIGNoYXJhY3RlcnMgbG93ZXIgb3IgdXBwZXJjYXNlLgoKIyMjIFF1ZXN0aW9ucwoxLiBVc2UgdGhlIGFjY291bnRzIHRhYmxlIHRvIGNyZWF0ZSBmaXJzdCBhbmQgbGFzdCBuYW1lIGNvbHVtbnMgdGhhdCBob2xkIHRoZSBmaXJzdCBhbmQgbGFzdCBuYW1lcyBmb3IgdGhlIHByaW1hcnlfcG9jLiAKYGBge3NxbH0KU0VMRUNUIExFRlQocHJpbWFyeV9wb2MsIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSAtMSApIGZpcnN0X25hbWUsIApSSUdIVChwcmltYXJ5X3BvYywgTEVOR1RIKHByaW1hcnlfcG9jKSAtIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSkgbGFzdF9uYW1lCkZST00gYWNjb3VudHM7CmBgYAoyLiBOb3cgc2VlIGlmIHlvdSBjYW4gZG8gdGhlIHNhbWUgdGhpbmcgZm9yIGV2ZXJ5IHJlcCBuYW1lIGluIHRoZSBzYWxlc19yZXAgdGFibGUuIEFnYWluIHByb3ZpZGUgZmlyc3QgYW5kIGxhc3QgbmFtZSBjb2x1bW5zLgpgYGB7c3FsfQpTRUxFQ1QgTEVGVChuYW1lLCBTVFJQT1MobmFtZSwgJyAnKSAtMSApIGZpcnN0X25hbWUsIAogICAgICAgUklHSFQobmFtZSwgTEVOR1RIKG5hbWUpIC0gU1RSUE9TKG5hbWUsICcgJykpIGxhc3RfbmFtZQpGUk9NIHNhbGVzX3JlcHM7CmBgYAoKIyMgQ09OQ0FUCkVhY2ggb2YgdGhlc2Ugd2lsbCBhbGxvdyB5b3UgdG8gY29tYmluZSBjb2x1bW5zIHRvZ2V0aGVyIGFjcm9zcyByb3dzLiBGb3IgZXhhbXBsZSwgZmlyc3QgYW5kIGxhc3QgbmFtZXMgc3RvcmVkIGluIHNlcGFyYXRlIGNvbHVtbnMgY291bGQgYmUgY29tYmluZWQgdG9nZXRoZXIgdG8gY3JlYXRlIGEgZnVsbCBuYW1lOiBDT05DQVQoZmlyc3RfbmFtZSwgJyAnLCBsYXN0X25hbWUpIG9yIHdpdGggcGlwaW5nIGFzIGZpcnN0X25hbWUgfHwgJyAnIHx8IGxhc3RfbmFtZS4KCiMjIyBRdWVzdGlvbnMKMS4gRWFjaCBjb21wYW55IGluIHRoZSBhY2NvdW50cyB0YWJsZSB3YW50cyB0byBjcmVhdGUgYW4gZW1haWwgYWRkcmVzcyBmb3IgZWFjaCBwcmltYXJ5X3BvYy4gVGhlIGVtYWlsIGFkZHJlc3Mgc2hvdWxkIGJlIHRoZSBmaXJzdCBuYW1lIG9mIHRoZSBwcmltYXJ5X3BvYyAuIGxhc3QgbmFtZSBwcmltYXJ5X3BvYyBAIGNvbXBhbnkgbmFtZSAuY29tLgpgYGB7c3FsfQpXSVRIIHQxIEFTICgKIFNFTEVDVCBMRUZUKHByaW1hcnlfcG9jLCAgICAgU1RSUE9TKHByaW1hcnlfcG9jLCAnICcpIC0xICkgZmlyc3RfbmFtZSwgIFJJR0hUKHByaW1hcnlfcG9jLCBMRU5HVEgocHJpbWFyeV9wb2MpIC0gU1RSUE9TKHByaW1hcnlfcG9jLCAnICcpKSBsYXN0X25hbWUsIG5hbWUKIEZST00gYWNjb3VudHMpClNFTEVDVCBmaXJzdF9uYW1lLCBsYXN0X25hbWUsIENPTkNBVChmaXJzdF9uYW1lLCAnLicsIGxhc3RfbmFtZSwgJ0AnLCBuYW1lLCAnLmNvbScpCkZST00gdDE7CmBgYAoKMi5Zb3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IGluIHRoZSBwcmV2aW91cyBzb2x1dGlvbiBzb21lIG9mIHRoZSBjb21wYW55IG5hbWVzIGluY2x1ZGUgc3BhY2VzLCB3aGljaCB3aWxsIGNlcnRhaW5seSBub3Qgd29yayBpbiBhbiBlbWFpbCBhZGRyZXNzLiBTZWUgaWYgeW91IGNhbiBjcmVhdGUgYW4gZW1haWwgYWRkcmVzcyB0aGF0IHdpbGwgd29yayBieSByZW1vdmluZyBhbGwgb2YgdGhlIHNwYWNlcyBpbiB0aGUgYWNjb3VudCBuYW1lLCBidXQgb3RoZXJ3aXNlIHlvdXIgc29sdXRpb24gc2hvdWxkIGJlIGp1c3QgYXMgaW4gcXVlc3Rpb24gMS4gU29tZSBoZWxwZnVsIGRvY3VtZW50YXRpb24gaXMgaGVyZS4KYGBge3NxbH0KV0lUSCB0MSBBUyAoCiBTRUxFQ1QgTEVGVChwcmltYXJ5X3BvYywgICAgIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSAtMSApIGZpcnN0X25hbWUsICBSSUdIVChwcmltYXJ5X3BvYywgTEVOR1RIKHByaW1hcnlfcG9jKSAtIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSkgbGFzdF9uYW1lLCBuYW1lCiBGUk9NIGFjY291bnRzKQpTRUxFQ1QgZmlyc3RfbmFtZSwgbGFzdF9uYW1lLCBDT05DQVQoZmlyc3RfbmFtZSwgJy4nLCBsYXN0X25hbWUsICdAJywgUkVQTEFDRShuYW1lLCAnICcsICcnKSwgJy5jb20nKQpGUk9NICB0MTsKYGBgCgozLldlIHdvdWxkIGFsc28gbGlrZSB0byBjcmVhdGUgYW4gaW5pdGlhbCBwYXNzd29yZCwgd2hpY2ggdGhleSB3aWxsIGNoYW5nZSBhZnRlciB0aGVpciBmaXJzdCBsb2cgaW4uIFRoZSBmaXJzdCBwYXNzd29yZCB3aWxsIGJlIHRoZSBmaXJzdCBsZXR0ZXIgb2YgdGhlIHByaW1hcnlfcG9jJ3MgZmlyc3QgbmFtZSAobG93ZXJjYXNlKSwgdGhlbiB0aGUgbGFzdCBsZXR0ZXIgb2YgdGhlaXIgZmlyc3QgbmFtZSAobG93ZXJjYXNlKSwgdGhlIGZpcnN0IGxldHRlciBvZiB0aGVpciBsYXN0IG5hbWUgKGxvd2VyY2FzZSksIHRoZSBsYXN0IGxldHRlciBvZiB0aGVpciBsYXN0IG5hbWUgKGxvd2VyY2FzZSksIHRoZSBudW1iZXIgb2YgbGV0dGVycyBpbiB0aGVpciBmaXJzdCBuYW1lLCB0aGUgbnVtYmVyIG9mIGxldHRlcnMgaW4gdGhlaXIgbGFzdCBuYW1lLCBhbmQgdGhlbiB0aGUgbmFtZSBvZiB0aGUgY29tcGFueSB0aGV5IGFyZSB3b3JraW5nIHdpdGgsIGFsbCBjYXBpdGFsaXplZCB3aXRoIG5vIHNwYWNlcy4KYGBge3NxbH0KV0lUSCB0MSBBUyAoCiBTRUxFQ1QgTEVGVChwcmltYXJ5X3BvYywgICAgIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSAtMSApIGZpcnN0X25hbWUsICBSSUdIVChwcmltYXJ5X3BvYywgTEVOR1RIKHByaW1hcnlfcG9jKSAtIFNUUlBPUyhwcmltYXJ5X3BvYywgJyAnKSkgbGFzdF9uYW1lLCBuYW1lCiBGUk9NIGFjY291bnRzKQpTRUxFQ1QgZmlyc3RfbmFtZSwgbGFzdF9uYW1lLCBDT05DQVQoZmlyc3RfbmFtZSwgJy4nLCBsYXN0X25hbWUsICdAJywgbmFtZSwgJy5jb20nKSwgTEVGVChMT1dFUihmaXJzdF9uYW1lKSwgMSkgfHwgUklHSFQoTE9XRVIoZmlyc3RfbmFtZSksIDEpIHx8IExFRlQoTE9XRVIobGFzdF9uYW1lKSwgMSkgfHwgUklHSFQoTE9XRVIobGFzdF9uYW1lKSwgMSkgfHwgTEVOR1RIKGZpcnN0X25hbWUpIHx8IExFTkdUSChsYXN0X25hbWUpIHx8IFJFUExBQ0UoVVBQRVIobmFtZSksICcgJywgJycpCkZST00gdDE7CmBgYAoKIyMgQ0FTVAplLmcuIElmIHlvdSBoYXZlIHRocmVlIHNlcGVyYXRlIGNvbHVtbnMgZm9yIGRheSwgbW9udGggYW5kIHllYXIuIFdlIGNhbiBjaGFuZ2UgdGhlIG1vbnRoIG5hbWUgdG8gYSBudW1iZXIgYW5kIGNvbmNhdGVuYXRlIHRoZXNlIHRocmVlIGNvbHVtbnMgd2l0aCBoeXBoZW5zIGFuZCB0ZWxsIHRoZSBkYXRhYmFzZSB0aGF0IHRoZSByZXN1bHQgaXMgYSBkYXRlLgpgYGB7c3FsfQpTRUxFQ1QgKiwKICBEQVRFX1BBUlQoJ21vbnRoJywgVE9fREFURShtb250aCwgJ21vbnRoJykpIEFTIGNsZWFuX21vbnRoIC8qQ29udmVydCBtb250aCBuYW1lcyB0byBudW1iZXJzICovCiAgeWVhciB8fCAnLScgfHwgREFURV9QQVJUKCdtb250aCcsIFRPX0RBVEUobW9udGgsICdtb250aCcpKSB8fCAnLScgfHwgZGF5IEFTIGNvbmNhdGVuYXRlZF9kYXRlIAogIENBU1QgKHllYXIgfHwgJy0nIHx8IERBVEVfUEFSVCgnbW9udGgnLCBUT19EQVRFKG1vbnRoLCAnbW9udGgnKSkgfHwgJy0nIHx8IGRheSBBUyBkYXRlKSBBUyBmb3JtYXR0ZWRfZGF0ZQpGUk9NIGFkX2NsaWNrcwpgYGAKREFURV9QQVJUKCdtb250aCcsIFRPX0RBVEUobW9udGgsICdtb250aCcpKSBoZXJlIGNoYW5nZWQgYSBtb250aCBuYW1lIGludG8gdGhlIG51bWJlciBhc3NvY2lhdGVkIHdpdGggdGhhdCBwYXJ0aWN1bGFyIG1vbnRoLgpUaGVuIHlvdSBjYW4gY2hhbmdlIGEgc3RyaW5nIHRvIGEgZGF0ZSB1c2luZyBDQVNULiBDQVNUIGlzIGFjdHVhbGx5IHVzZWZ1bCB0byBjaGFuZ2UgbG90cyBvZiBjb2x1bW4gdHlwZXMuIENvbW1vbmx5IHlvdSBtaWdodCBiZSBkb2luZyBhcyB5b3Ugc2F3IGhlcmUsIHdoZXJlIHlvdSBjaGFuZ2UgYSBzdHJpbmcgdG8gYSBkYXRlIHVzaW5nIENBU1QoZGF0ZV9jb2x1bW4gQVMgREFURSkuIEhvd2V2ZXIsIHlvdSBtaWdodCB3YW50IHRvIG1ha2Ugb3RoZXIgY2hhbmdlcyB0byB5b3VyIGNvbHVtbnMgaW4gdGVybXMgb2YgdGhlaXIgZGF0YSB0eXBlcy4gCkluIHRoaXMgZXhhbXBsZSwgaW5zdGVhZCBvZiBDQVNUKGRhdGVfY29sdW1uIEFTIERBVEUpLCB5b3UgY2FuIHVzZSBkYXRlX2NvbHVtbjo6REFURS4KCk1vc3Qgb2YgdGhlIGZ1bmN0aW9ucyBwcmVzZW50ZWQgaW4gdGhpcyBsZXNzb24gYXJlIHNwZWNpZmljIHRvIHN0cmluZ3MuIFRoZXkgd29u4oCZdCB3b3JrIHdpdGggZGF0ZXMsIGludGVnZXJzIG9yIGZsb2F0aW5nLXBvaW50IG51bWJlcnMuIEhvd2V2ZXIsIHVzaW5nIGFueSBvZiB0aGVzZSBmdW5jdGlvbnMgd2lsbCBhdXRvbWF0aWNhbGx5IGNoYW5nZSB0aGUgZGF0YSB0byB0aGUgYXBwcm9wcmlhdGUgdHlwZS4KTEVGVCwgUklHSFQsIGFuZCBUUklNIGFyZSBhbGwgdXNlZCB0byBzZWxlY3Qgb25seSBjZXJ0YWluIGVsZW1lbnRzIG9mIHN0cmluZ3MsIGJ1dCB1c2luZyB0aGVtIHRvIHNlbGVjdCBlbGVtZW50cyBvZiBhIG51bWJlciBvciBkYXRlIHdpbGwgdHJlYXQgdGhlbSBhcyBzdHJpbmdzIGZvciB0aGUgcHVycG9zZSBvZiB0aGUgZnVuY3Rpb24uIFRob3VnaCB3ZSBkaWRuJ3QgY292ZXIgVFJJTSBpbiB0aGlzIGxlc3NvbiBleHBsaWNpdGx5LCBpdCBjYW4gYmUgdXNlZCB0byByZW1vdmUgY2hhcmFjdGVycyBmcm9tIHRoZSBiZWdpbm5pbmcgYW5kIGVuZCBvZiBhIHN0cmluZy4gVGhpcyBjYW4gcmVtb3ZlIHVud2FudGVkIHNwYWNlcyBhdCB0aGUgYmVnaW5uaW5nIG9yIGVuZCBvZiBhIHJvdyB0aGF0IG9mdGVuIGhhcHBlbiB3aXRoIGRhdGEgYmVpbmcgbW92ZWQgZnJvbSBFeGNlbCBvciBvdGhlciBzdG9yYWdlIHN5c3RlbXMuCgojIyMgUXVlc3Rpb25zCjEuIEluIHRoZSBnaXZlbiBkYXRhc2V0IHRoZSBmb3JtYXQgb2YgdGhlIGRhdGUgY29sdW1uIGlzIG1tL2RkL3l5eXkgd2l0aCB0aW1lcyB0aGF0IGFyZSBub3QgY29ycmVjdCBhbHNvIGF0IHRoZSBlbmQgb2YgdGhlIGRhdGUuIFJlbWVtYmVyIGRhdGVzIHNob3VsZCBiZSBpbiB0aGUgZm9ybWF0IHl5eXktbW0tZGQuIFdyaXRlIGEgcXVlcnkgdG8gY2hhbmdlIHRoZSBkYXRlIHRvIHRoZSBjb3JyZWN0IFNRTCBkYXRlIGZvcm1hdC4gVGhlbiB1c2UgZWl0aGVyIENBU1Qgb3IgOjogdG8gY29udmVydCB0byBhIGRhdGUuIApgYGB7c3FsfQpTRUxFQ1QgZGF0ZSBvcmlnX2RhdGUsIChTVUJTVFIoZGF0ZSwgNywgNCkgfHwgJy0nIHx8IExFRlQoZGF0ZSwgMikgfHwgJy0nIHx8IFNVQlNUUihkYXRlLCA0LCAyKSk6OkRBVEUgbmV3X2RhdGUKRlJPTSBzZl9jcmltZV9kYXRhOwpgYGAKCiMjIENPQUxFU0NFCk9jY2Fzc2lvbmFsbHkgeW91J2xsIGVuZCB1cCB3aXRoIGEgZGF0c2V0IHRoYXQgY29udGFpbnMgc29tZSBudWxscyB0aGF0IHlvdSdkIHByZWZlciB0byBoYXZlIHNvbWUgYWN0dWFsIHZhbHVlcy4gRm9yIGUuZy4gd2UgbWF5IHdhbnQgdG8gbGFiZWwgYSBOVUxMIHZhbHVlIGluIHRoZSBwcmltYXJ5X3BvYyBjb2x1bW4gYXMgIm5vIHBvYyIgc28gdGhlIHJlc3VsdHMgd2lsbCBiZSBlYXNpbHkgdW5kZXJzdGFuYWJsZS4gWW91IGNhbiB1c2UgQ09BTEVTQ0UgdG8gcmVwbGFjZSB0aGUgTlVMTCB2YWx1ZXMuIApgYGB7c3FsfQpTRUxFQ1QgKiwKICBDT0FMRVNDRShwcmltYXJ5X3BvYywgJ25vIFBPQycpIEFTIHByaW1hcnlfcG9jX21vZGlmaWVkCkZST00gYWNjb3VudHMKV0hFUkUgcHJpbWFyeV9wb2MgaXMgTlVMTDsKYGBgCkluIGdlbmVyYWwsIENPQUxFU0NFIHJldHVybnMgdGhlIGZpcnN0IG5vbi1OVUxMIHZhbHVlIHBhc3NlZCBmb3IgZWFjaCByb3cuCllvdSBjYW4gdXNlIENPQUxFU0NFIHRvIGZpbGwgTlVMTCB2YWx1ZXMgc28gdGhhdCB5b3UgZ2V5IHZhbHVlcyBpbiBldmVyeSBjZWxsLiBUaGlzIHdpbGwgaGF2ZSBhbiBlZmZlY3Qgb24gZnVuY3Rpb25zIHN1Y2ggYXMgQ09VTlQKCiMjIyBRdWVzdGlvbnMKMS4gVXNlIENPQUxTRUNFIHRvIGZpbGwgaW4gdGhlIGFjY291bnRzLmlkIGNvbHVtbiB3aXRoIHRoZSBhY2NvdW50LmlkIGZvciB0aGUgTlVMTCB2YWx1ZSAKYGBge3NxbH0KU0VMRUNUIENPQUxFU0NFKGEuaWQsIGEuaWQpIGZpbGxlZF9pZCwgYS5uYW1lLCBhLndlYnNpdGUsIGEubGF0LCBhLmxvbmcsIGEucHJpbWFyeV9wb2MsIGEuc2FsZXNfcmVwX2lkLCBvLioKRlJPTSBhY2NvdW50cyBhCkxFRlQgSk9JTiBvcmRlcnMgbwpPTiBhLmlkID0gby5hY2NvdW50X2lkCldIRVJFIG8udG90YWwgSVMgTlVMTDsKYGBgCgoyLiBVc2UgQ09BTFNFQ0UgdG8gZmlsbCBpbiB0aGUgb3JkZXJzLmFjY291bnRfaWQgY29sdW1uIHdpdGggdGhlIGFjY291bnQuaWQgZm9yIHRoZSBOVUxMIHZhbHVlIApgYGB7c3FsfQpTRUxFQ1QgQ09BTEVTQ0UoYS5pZCwgYS5pZCkgZmlsbGVkX2lkLCBhLm5hbWUsIGEud2Vic2l0ZSwgYS5sYXQsIGEubG9uZywgYS5wcmltYXJ5X3BvYywgYS5zYWxlc19yZXBfaWQsIENPQUxFU0NFKG8uYWNjb3VudF9pZCwgYS5pZCkgYWNjb3VudF9pZCwgby5vY2N1cnJlZF9hdCwgby5zdGFuZGFyZF9xdHksIG8uZ2xvc3NfcXR5LCBvLnBvc3Rlcl9xdHksIG8udG90YWwsIG8uc3RhbmRhcmRfYW10X3VzZCwgby5nbG9zc19hbXRfdXNkLCBvLnBvc3Rlcl9hbXRfdXNkLCBvLnRvdGFsX2FtdF91c2QKRlJPTSBhY2NvdW50cyBhCkxFRlQgSk9JTiBvcmRlcnMgbwpPTiBhLmlkID0gby5hY2NvdW50X2lkCldIRVJFIG8udG90YWwgSVMgTlVMTDsKYGBgCgozLiBVU0UgQ09BTEVTQ0UgdG8gZmlsbCBpbiBlY2FoIG9mIHRoZSBxdHkgYW5kIHVzZCBjb2x1bW5zIHdpdGggMC4KYGBge3NxbH0KU0VMRUNUIENPQUxFU0NFKGEuaWQsIGEuaWQpIGZpbGxlZF9pZCwgYS5uYW1lLCBhLndlYnNpdGUsIGEubGF0LCBhLmxvbmcsIGEucHJpbWFyeV9wb2MsIGEuc2FsZXNfcmVwX2lkLCBDT0FMRVNDRShvLmFjY291bnRfaWQsIGEuaWQpIGFjY291bnRfaWQsIG8ub2NjdXJyZWRfYXQsIENPQUxFU0NFKG8uc3RhbmRhcmRfcXR5LCAwKSBzdGFuZGFyZF9xdHksIENPQUxFU0NFKG8uZ2xvc3NfcXR5LDApIGdsb3NzX3F0eSwgQ09BTEVTQ0Uoby5wb3N0ZXJfcXR5LDApIHBvc3Rlcl9xdHksIENPQUxFU0NFKG8udG90YWwsMCkgdG90YWwsIENPQUxFU0NFKG8uc3RhbmRhcmRfYW10X3VzZCwwKSBzdGFuZGFyZF9hbXRfdXNkLCBDT0FMRVNDRShvLmdsb3NzX2FtdF91c2QsMCkgZ2xvc3NfYW10X3VzZCwgQ09BTEVTQ0Uoby5wb3N0ZXJfYW10X3VzZCwwKSBwb3N0ZXJfYW10X3VzZCwgQ09BTEVTQ0Uoby50b3RhbF9hbXRfdXNkLDApIHRvdGFsX2FtdF91c2QKRlJPTSBhY2NvdW50cyBhCkxFRlQgSk9JTiBvcmRlcnMgbwpPTiBhLmlkID0gby5hY2NvdW50X2lkCldIRVJFIG8udG90YWwgSVMgTlVMTDsKYGBgCgoqKioK